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