001package org.hl7.fhir.r5.renderers.utils;
002
003import java.io.IOException;
004import java.time.ZoneId;
005import java.time.format.DateTimeFormatter;
006import java.util.ArrayList;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Locale;
010import java.util.Map;
011
012import org.hl7.fhir.exceptions.FHIRException;
013import org.hl7.fhir.exceptions.FHIRFormatError;
014import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
015import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
016import org.hl7.fhir.r5.context.IWorkerContext;
017import org.hl7.fhir.r5.elementmodel.Element;
018import org.hl7.fhir.r5.model.Base;
019import org.hl7.fhir.r5.model.DomainResource;
020import org.hl7.fhir.r5.renderers.utils.Resolver.IReferenceResolver;
021import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
022import org.hl7.fhir.utilities.FhirPublication;
023import org.hl7.fhir.utilities.MarkDownProcessor;
024import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
025import org.hl7.fhir.utilities.Utilities;
026import org.hl7.fhir.utilities.validation.ValidationOptions;
027
028public class RenderingContext {
029
030  // provides liquid templates, if they are available for the content
031  public interface ILiquidTemplateProvider {
032    String findTemplate(RenderingContext rcontext, DomainResource r);
033    String findTemplate(RenderingContext rcontext, String resourceName);
034  }
035
036  // parses xml to an XML instance. Whatever codes provides this needs to provide something that parses the right version 
037  public interface ITypeParser {
038    Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException ;
039    Base parseType(Element base) throws FHIRFormatError, IOException, FHIRException ;
040  }
041
042  /**
043   * What kind of user the renderer is targeting - end users, or technical users
044   * 
045   * This affects the way codes and references are rendered
046   * 
047   * @author graha
048   *
049   */
050  public enum ResourceRendererMode {
051    /**
052     * The user has no interest in the contents of the FHIR resource, and just wants to see the data
053     * 
054     */
055    END_USER,
056    
057    /**
058     * The user wants to see the resource, but a technical view so they can see what's going on with the content
059     */
060    TECHNICAL
061  }
062
063  public enum GenerationRules {
064    /**
065     * The output must be valid XHTML for a resource: no active content, etc. The only external dependency allowed is fhir.css 
066     */
067    VALID_RESOURCE,
068    
069    /**
070     * The output must be valid for an implementation guide according ot the base FHIR template. 
071     * This means active content is allowed, though the default presentation must be *show everything* for balloting purposes
072     * Active content is allowed 
073     */
074    IG_PUBLISHER
075  }
076  
077  public enum StructureDefinitionRendererMode {
078    SUMMARY, // 5 cells: tree/name | flags | cardinality | type | details
079    BINDINGS, // tree/name + column for each kind of binding found, cells are lists of bindings 
080    OBLIGATIONS, // tree/name + column for each actor that has obligations
081  }
082  
083  public enum QuestionnaireRendererMode {
084    /**
085     * A visual presentation of the questionnaire, with a set of property panes that can be toggled on and off.
086     * Note that this is not the same as how the questionnaire would like on a form filler, since all dynamic behavior is ignored
087     */
088    FORM,
089
090    /**
091     * a structured tree that presents the content of the questionnaire in a logical fashion
092     */
093    TREE,   
094
095    /**
096     * A structured tree that presents the enableWhen, terminology and expression bindings for the questionnaire 
097     */
098    LOGIC,
099
100    /**
101     * A presentation that lists all the items, with full details about them 
102     */
103    DEFNS, 
104
105    /**
106     * Rendered links to various openly available Form Filler applications that know how to render a questionnaire published in a package 
107     */
108    LINKS
109  }
110
111  public enum KnownLinkType {
112    SELF,  // absolute link to where the content is to be found (only used in a few circumstances when making external references to tools)
113    SPEC,  // version specific link to core specification
114    JSON_NAMES
115    
116  }
117  private IWorkerContext worker;
118  private MarkDownProcessor markdown;
119  private ResourceRendererMode mode;
120  private GenerationRules rules;
121  private IReferenceResolver resolver;
122  private ILiquidTemplateProvider templateProvider;
123  private IEvaluationContext services;
124  private ITypeParser parser;
125
126  private String lang;
127  private String localPrefix; // relative link within local context
128  private int headerLevelContext;
129  private boolean canonicalUrlsAsLinks;
130  private boolean pretty;
131  private boolean header;
132  private boolean contained;
133
134  private ValidationOptions terminologyServiceOptions = new ValidationOptions();
135  private boolean noSlowLookup;
136  private String tooCostlyNoteEmpty;
137  private String tooCostlyNoteNotEmpty;
138  private String tooCostlyNoteEmptyDependent;
139  private String tooCostlyNoteNotEmptyDependent;
140  private List<String> codeSystemPropList = new ArrayList<>();
141
142  private ProfileUtilities profileUtilitiesR;
143  private String definitionsTarget;
144  private String destDir;
145  private boolean inlineGraphics;
146
147  private QuestionnaireRendererMode questionnaireMode = QuestionnaireRendererMode.FORM;
148  private StructureDefinitionRendererMode structureMode = StructureDefinitionRendererMode.SUMMARY;
149  
150  private boolean addGeneratedNarrativeHeader = true;
151  private boolean showComments = false;
152
153  private FhirPublication targetVersion;
154  private Locale locale;
155  private ZoneId timeZoneId;
156  private DateTimeFormatter dateTimeFormat;
157  private DateTimeFormatter dateFormat;
158  private DateTimeFormatter dateYearFormat;
159  private DateTimeFormatter dateYearMonthFormat;
160  private boolean copyButton;
161  private ProfileKnowledgeProvider pkp;
162  
163  private Map<KnownLinkType, String> links = new HashMap<>();
164  /**
165   * 
166   * @param context - access to all related resources that might be needed
167   * @param markdown - appropriate markdown processing engine 
168   * @param terminologyServiceOptions - options to use when looking up codes
169   * @param specLink - path to FHIR specification
170   * @param lang - langauage to render in
171   */
172  public RenderingContext(IWorkerContext worker, MarkDownProcessor markdown, ValidationOptions terminologyServiceOptions, String specLink, String localPrefix, String lang, ResourceRendererMode mode, GenerationRules rules) {
173    super();
174    this.worker = worker;
175    this.markdown = markdown;
176    this.lang = lang;
177    this.links.put(KnownLinkType.SPEC, specLink);
178    this.localPrefix = localPrefix;
179    this.mode = mode;
180    this.rules = rules;
181    if (terminologyServiceOptions != null) {
182      this.terminologyServiceOptions = terminologyServiceOptions;
183    }
184 // default to US locale - discussion here: https://github.com/hapifhir/org.hl7.fhir.core/issues/666
185    this.locale = new Locale.Builder().setLanguageTag("en-US").build(); 
186  }
187  public RenderingContext copy() {
188    RenderingContext res = new RenderingContext(worker, markdown, terminologyServiceOptions, getLink(KnownLinkType.SPEC), localPrefix, lang, mode, rules);
189
190    res.resolver = resolver;
191    res.templateProvider = templateProvider;
192    res.services = services;
193    res.parser = parser;
194
195    res.headerLevelContext = headerLevelContext;
196    res.canonicalUrlsAsLinks = canonicalUrlsAsLinks;
197    res.pretty = pretty;
198    res.contained = contained;
199    
200    res.noSlowLookup = noSlowLookup;
201    res.tooCostlyNoteEmpty = tooCostlyNoteEmpty;
202    res.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty;
203    res.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent;
204    res.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent;
205    res.codeSystemPropList.addAll(codeSystemPropList);
206
207    res.profileUtilitiesR = profileUtilitiesR;
208    res.definitionsTarget = definitionsTarget;
209    res.destDir = destDir;
210    res.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader;
211    res.questionnaireMode = questionnaireMode;
212    res.structureMode = structureMode;
213    res.header = header;
214    res.links.putAll(links);
215    res.inlineGraphics = inlineGraphics;
216    res.timeZoneId = timeZoneId;
217    res.dateTimeFormat = dateTimeFormat;
218    res.dateFormat = dateFormat;
219    res.dateYearFormat = dateYearFormat;
220    res.dateYearMonthFormat = dateYearMonthFormat;
221    res.targetVersion = targetVersion;
222    res.locale = locale;
223    res.showComments = showComments;
224    res.copyButton = copyButton;
225    res.pkp = pkp;
226
227    res.terminologyServiceOptions = terminologyServiceOptions.copy();
228    return res;
229  }
230  
231
232  public IWorkerContext getContext() {
233    return worker;
234  }
235
236
237  
238  // -- 2. Markdown support -------------------------------------------------------
239
240  public ProfileUtilities getProfileUtilities() {
241    if (profileUtilitiesR == null) {
242      profileUtilitiesR = new ProfileUtilities(worker, null, pkp);
243    }
244    return profileUtilitiesR;
245  }
246
247  public IWorkerContext getWorker() {
248    return worker;
249  }
250
251  public boolean isCanonicalUrlsAsLinks() {
252    return canonicalUrlsAsLinks;
253  }
254
255  public RenderingContext setCanonicalUrlsAsLinks(boolean canonicalUrlsAsLinks) {
256    this.canonicalUrlsAsLinks = canonicalUrlsAsLinks;
257    return this;
258  }
259
260  public MarkDownProcessor getMarkdown() {
261    if (markdown == null) {
262      markdown = new MarkDownProcessor(Dialect.COMMON_MARK);
263    }
264    return markdown;
265  }
266
267  public String getLang() {
268    return lang;
269  }
270
271  public String getLocalPrefix() {
272    return localPrefix;
273  }
274
275  public ValidationOptions getTerminologyServiceOptions() {
276    return terminologyServiceOptions;
277  }
278
279
280  public String getTooCostlyNoteEmpty() {
281    return tooCostlyNoteEmpty;
282  }
283
284  public RenderingContext setTooCostlyNoteEmpty(String tooCostlyNoteEmpty) {
285    this.tooCostlyNoteEmpty = tooCostlyNoteEmpty;
286    return this;
287  }
288
289  public String getTooCostlyNoteNotEmpty() {
290    return tooCostlyNoteNotEmpty;
291  }
292
293  public RenderingContext setTooCostlyNoteNotEmpty(String tooCostlyNoteNotEmpty) {
294    this.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty;
295    return this;
296  }
297
298  public String getTooCostlyNoteEmptyDependent() {
299    return tooCostlyNoteEmptyDependent;
300  }
301
302  public RenderingContext setTooCostlyNoteEmptyDependent(String tooCostlyNoteEmptyDependent) {
303    this.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent;
304    return this;
305  }
306
307  public String getTooCostlyNoteNotEmptyDependent() {
308    return tooCostlyNoteNotEmptyDependent;
309  }
310
311  public RenderingContext setTooCostlyNoteNotEmptyDependent(String tooCostlyNoteNotEmptyDependent) {
312    this.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent;
313    return this;
314  }
315
316  public int getHeaderLevelContext() {
317    return headerLevelContext;
318  }
319
320  public RenderingContext setHeaderLevelContext(int headerLevelContext) {
321    this.headerLevelContext = headerLevelContext;
322    return this;
323  }
324
325  public IReferenceResolver getResolver() {
326    return resolver;
327  }
328
329  public RenderingContext setResolver(IReferenceResolver resolver) {
330    this.resolver = resolver;
331    return this;
332  }
333
334  public RenderingContext setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
335    this.terminologyServiceOptions = terminologyServiceOptions;
336    return this;
337  }
338
339  public boolean isNoSlowLookup() {
340    return noSlowLookup;
341  }
342
343  public RenderingContext setNoSlowLookup(boolean noSlowLookup) {
344    this.noSlowLookup = noSlowLookup;
345    return this;
346  }
347
348  public String getDefinitionsTarget() {
349    return definitionsTarget;
350  }
351
352  public RenderingContext setDefinitionsTarget(String definitionsTarget) {
353    this.definitionsTarget = definitionsTarget;
354    return this;
355  }
356
357  public String getDestDir() {
358    return destDir;
359  }
360
361  public RenderingContext setDestDir(String destDir) {
362    this.destDir = destDir;
363    return this;
364  }
365
366  public RenderingContext setProfileUtilities(ProfileUtilities profileUtilities) {
367    this.profileUtilitiesR = profileUtilities;
368    if (pkp == null && profileUtilities.getPkp() != null) {
369      pkp = profileUtilities.getPkp();
370    }
371    return this;
372  }
373
374  public ILiquidTemplateProvider getTemplateProvider() {
375    return templateProvider;
376  }
377
378  public RenderingContext setTemplateProvider(ILiquidTemplateProvider templateProvider) {
379    this.templateProvider = templateProvider;
380    return this;
381  }
382
383  public IEvaluationContext getServices() {
384    return services;
385  }
386
387  public RenderingContext setServices(IEvaluationContext services) {
388    this.services = services;
389    return this;
390  }
391
392  public boolean isPretty() {
393    return pretty;
394  }
395
396  public RenderingContext setPretty(boolean pretty) {
397    this.pretty = pretty;
398    return this;
399  }
400
401  public ITypeParser getParser() {
402    return parser;
403  }
404
405  public RenderingContext setParser(ITypeParser parser) {
406    this.parser = parser;
407    return this;
408  }
409
410
411  public List<String> getCodeSystemPropList() {
412    return codeSystemPropList;
413  }
414
415  public RenderingContext setCodeSystemPropList(List<String> codeSystemPropList) {
416    this.codeSystemPropList = codeSystemPropList;
417    return this;
418  }
419
420
421  public boolean isInlineGraphics() {
422    return inlineGraphics;
423  }
424
425  public RenderingContext setInlineGraphics(boolean inlineGraphics) {
426    this.inlineGraphics = inlineGraphics;
427    return this;
428  }
429
430  public boolean isHeader() {
431    return header;
432  }
433
434  public RenderingContext setHeader(boolean header) {
435    this.header = header;
436    return this;
437  }
438
439  public QuestionnaireRendererMode getQuestionnaireMode() {
440    return questionnaireMode;
441  }
442
443  public RenderingContext setQuestionnaireMode(QuestionnaireRendererMode questionnaireMode) {
444    this.questionnaireMode = questionnaireMode;
445    return this;
446  }
447  
448  public StructureDefinitionRendererMode getStructureMode() {
449    return structureMode;
450  }
451
452  public RenderingContext setStructureMode(StructureDefinitionRendererMode structureMode) {
453    this.structureMode = structureMode;
454    return this;
455  }
456
457  public String fixReference(String ref) {
458    if (!Utilities.isAbsoluteUrl(ref)) {
459      return (localPrefix == null ? "" : localPrefix)+ref;
460    }
461    if (ref.startsWith("http://hl7.org/fhir") && !ref.substring(20).contains("/")) {
462      return getLink(KnownLinkType.SPEC)+ref.substring(20);
463    }
464    return ref;
465  }
466
467  public RenderingContext setLang(String lang) {
468    this.lang = lang;
469    return this;
470  }
471
472  public RenderingContext setLocalPrefix(String localPrefix) {
473    this.localPrefix = localPrefix;
474    return this;
475  }
476
477  public boolean isAddGeneratedNarrativeHeader() {
478    return addGeneratedNarrativeHeader;
479  }
480
481  public RenderingContext setAddGeneratedNarrativeHeader(boolean addGeneratedNarrativeHeader) {
482    this.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader;
483    return this;
484   }
485
486  public FhirPublication getTargetVersion() {
487    return targetVersion;
488  }
489
490  public RenderingContext setTargetVersion(FhirPublication targetVersion) {
491    this.targetVersion = targetVersion;
492    return this;
493  }
494
495  public boolean isTechnicalMode() {
496    return mode == ResourceRendererMode.TECHNICAL;
497  }
498
499  public boolean hasLocale() {
500    return locale != null;
501  }
502  
503  public Locale getLocale() {
504    if (locale == null) {
505      return Locale.getDefault();
506    } else { 
507      return locale;
508    }
509  }
510
511  public RenderingContext setLocale(Locale locale) {
512    this.locale = locale;
513    return this;
514  }
515
516
517  /**
518   * if the timezone is null, the rendering will default to the source timezone
519   * in the resource
520   * 
521   * Note that if you're working server side, the FHIR project recommends the use
522   * of the Date header so that clients know what timezone the server defaults to,
523   * 
524   * There is no standard way for the server to know what the client timezone is. 
525   * In the case where the client timezone is unknown, the timezone should be null
526   *
527   * @return the specified timezone to render in
528   */
529  public ZoneId getTimeZoneId() {
530    return timeZoneId;
531  }
532
533  public RenderingContext setTimeZoneId(ZoneId timeZoneId) {
534    this.timeZoneId = timeZoneId;
535    return this;
536  }
537
538
539  /**
540   * In the absence of a specified format, the renderers will default to 
541   * the FormatStyle.MEDIUM for the current locale.
542   * 
543   * @return the format to use
544   */
545  public DateTimeFormatter getDateTimeFormat() {
546    return this.dateTimeFormat;
547  }
548
549  public RenderingContext setDateTimeFormat(DateTimeFormatter dateTimeFormat) {
550    this.dateTimeFormat = dateTimeFormat;
551    return this;
552  }
553
554  public RenderingContext setDateTimeFormatString(String dateTimeFormat) {
555    this.dateTimeFormat = DateTimeFormatter.ofPattern(dateTimeFormat);
556    return this;
557  }
558
559  /**
560   * In the absence of a specified format, the renderers will default to 
561   * the FormatStyle.MEDIUM for the current locale.
562   * 
563   * @return the format to use
564   */
565  public DateTimeFormatter getDateFormat() {
566    return this.dateFormat;
567  }
568
569  public RenderingContext setDateFormat(DateTimeFormatter dateFormat) {
570    this.dateFormat = dateFormat;
571    return this;
572  }
573
574  public RenderingContext setDateFormatString(String dateFormat) {
575    this.dateFormat = DateTimeFormatter.ofPattern(dateFormat);
576    return this;
577  }
578
579  public DateTimeFormatter getDateYearFormat() {
580    return dateYearFormat;
581  }
582
583  public RenderingContext setDateYearFormat(DateTimeFormatter dateYearFormat) {
584    this.dateYearFormat = dateYearFormat;
585    return this;
586  }
587
588  public RenderingContext setDateYearFormatString(String dateYearFormat) {
589    this.dateYearFormat = DateTimeFormatter.ofPattern(dateYearFormat);
590    return this;
591  }
592
593  public DateTimeFormatter getDateYearMonthFormat() {
594    return dateYearMonthFormat;
595  }
596
597  public RenderingContext setDateYearMonthFormat(DateTimeFormatter dateYearMonthFormat) {
598    this.dateYearMonthFormat = dateYearMonthFormat;
599    return this;
600  }
601
602  public RenderingContext setDateYearMonthFormatString(String dateYearMonthFormat) {
603    this.dateYearMonthFormat = DateTimeFormatter.ofPattern(dateYearMonthFormat);
604    return this;
605  }
606
607  public ResourceRendererMode getMode() {
608    return mode;
609  }
610
611  public RenderingContext setMode(ResourceRendererMode mode) {
612    this.mode = mode;
613    return this;
614  }
615
616  public boolean isContained() {
617    return contained;
618  }
619
620  public RenderingContext setContained(boolean contained) {
621    this.contained = contained;
622    return this;
623  }
624  public boolean isShowComments() {
625    return showComments;
626  }
627  public RenderingContext setShowComments(boolean showComments) {
628    this.showComments = showComments;
629    return this;
630  }
631  public boolean isCopyButton() {
632    return copyButton;
633  }
634  public RenderingContext setCopyButton(boolean copyButton) {
635    this.copyButton = copyButton;
636    return this;
637  }
638  
639  public void setPkp(ProfileKnowledgeProvider pkp) {
640    this.pkp = pkp;
641  }
642  public ProfileKnowledgeProvider getPkp() {
643    return pkp;
644  }
645  
646  public boolean hasLink(KnownLinkType link) {
647    return links.containsKey(link);
648  }
649  
650  public String getLink(KnownLinkType link) {
651    return links.get(link);
652  }
653  public void addLink(KnownLinkType type, String link) {
654    links.put(type, link);
655    
656  }
657  public GenerationRules getRules() {
658    return rules;
659  }
660  public void setRules(GenerationRules rules) {
661    this.rules = rules;
662  }
663
664}