001package org.hl7.fhir.r5.renderers.utils;
002
003import java.io.IOException;
004import java.text.NumberFormat;
005import java.time.ZoneId;
006import java.time.format.DateTimeFormatter;
007import java.util.*;
008
009import lombok.Getter;
010import lombok.Setter;
011import org.hl7.fhir.exceptions.FHIRException;
012import org.hl7.fhir.exceptions.FHIRFormatError;
013import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
014import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
015import org.hl7.fhir.r5.context.ContextUtilities;
016import org.hl7.fhir.r5.context.IWorkerContext;
017import org.hl7.fhir.r5.elementmodel.Element;
018import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
019import org.hl7.fhir.r5.extensions.ExtensionUtilities;
020import org.hl7.fhir.r5.fhirpath.IHostApplicationServices;
021import org.hl7.fhir.r5.model.ActorDefinition;
022import org.hl7.fhir.r5.model.Base;
023import org.hl7.fhir.r5.model.DomainResource;
024import org.hl7.fhir.r5.model.Enumeration;
025import org.hl7.fhir.r5.model.PackageInformation;
026import org.hl7.fhir.r5.model.PrimitiveType;
027import org.hl7.fhir.r5.model.Resource;
028import org.hl7.fhir.r5.model.StringType;
029import org.hl7.fhir.r5.renderers.utils.Resolver.IReferenceResolver;
030import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
031
032import org.hl7.fhir.utilities.*;
033import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
034import org.hl7.fhir.utilities.i18n.RenderingI18nContext;
035import org.hl7.fhir.utilities.validation.ValidationOptions;
036
037/**
038 * Managing Language when rendering 
039 * 
040 * You can specify a language to use when rendering resources by setting the setLocale() on 
041 * the super class. The locale drives the following:
042 * - choice of java supplied rendering phrase, if translations are provided for the locale 
043 * - integer and date formats used (but see below for date formats)
044 * - automatic translation of coded values, if language supplements are available
045 * - choosing text representation considering the FHIR translation extension
046 *    
047 * By default, the locale is null, and the default locale for the underlying system is used. 
048 * If you set locale to a specific value, then that value will be used instead of the default locale.
049 *    
050 * By default, only a single language is rendered, based on the locale. Where resources contain
051 * multiple language content (designations in CodeSystem and ValueSet, or using the translation
052 * extension), you can control what languages are presented using the properties multiLanguagePolicy
053 * and languages
054 * - multiLanguagePolicy: NONE (default), DESIGNATIONS, ALL
055 * - languages: a list of allowed languages. Default is empty which means all languages in scope via multiLanguagePolicy
056 * 
057 * Managing Date/Time Formatting
058 * 
059 * This class has multiple parameters that influence date/time formatting when rendering resources 
060 * 
061 * - The default rendering is using the default java locale as above
062 * - If you setLocale() to something, then the defaults for the locale will be used 
063 * - Else you can set the values of dateTimeFormat, dateFormat, dateYearFormat and dateYearMonthFormat
064 * 
065 * If you set the value of locale, the values of dateTimeFormat, dateFormat, dateYearFormat and dateYearMonthFormat are 
066 * reset to the system defaults 
067 * 
068 * Timezones: by default, date/times are rendered in their source timezone 
069 * 
070 */
071@MarkedToMoveToAdjunctPackage
072public class RenderingContext extends RenderingI18nContext {
073
074
075  public enum DesignationMode {
076    ALL,
077    LANGUAGES,
078    NONE
079  }
080
081  public interface IResourceLinkResolver {
082    public <T extends Resource> T findLinkableResource(Class<T> class_, String uri) throws IOException;
083  }
084
085  public static class RenderingContextLangs {
086    
087    private final RenderingContext defLangRC;
088    private final Map<String, RenderingContext> langs = new HashMap<>();
089
090    public RenderingContextLangs(RenderingContext defLangRC) {
091      this.defLangRC = defLangRC;
092    }
093
094    public void seeLang(String lang, RenderingContext rc) {
095      this.langs.put(lang, rc);
096    }
097    
098    public RenderingContext get(String lang) {
099      if (lang == null || !langs.containsKey(lang)) {
100        return defLangRC;
101      } else {
102        return langs.get(lang);
103      }
104    }
105
106    public void setNoHeader(boolean b) {
107      defLangRC.setNoHeader(b);
108      for (RenderingContext rc : langs.values()) {
109        rc.setNoHeader(b);
110      }
111    }
112
113    public Collection<RenderingContext> langValues() {
114      return langs.values();
115    }
116  }
117
118  // provides liquid templates, if they are available for the content
119  public interface ILiquidTemplateProvider {
120    String findTemplate(RenderingContext rcontext, DomainResource r);
121    String findTemplate(RenderingContext rcontext, String resourceName);
122  }
123
124  // parses xml to an XML instance. Whatever codes provides this needs to provide something that parses the right version 
125  public interface ITypeParser {
126    Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException ;
127    Base parseType(Element base) throws FHIRFormatError, IOException, FHIRException ;
128  }
129
130  /**
131   * What kind of user the renderer is targeting - end users, or technical users
132   * 
133   * This affects the way codes and references are rendered
134   * 
135   * @author graha
136   *
137   */
138  public enum ResourceRendererMode {
139    /**
140     * The user has no interest in the contents of the FHIR resource, and just wants to see the data
141     * 
142     */
143    END_USER,
144    
145    /**
146     * The user wants to see the resource, but a technical view so they can see what's going on with the content - this includes content like the meta header
147     */
148    TECHNICAL
149  }
150
151  public enum GenerationRules {
152    /**
153     * The output must be valid XHTML for a resource: no active content, etc. The only external dependency allowed is fhir.css 
154     */
155    VALID_RESOURCE,
156    
157    /**
158     * The output must be valid for an implementation guide according ot the base FHIR template. 
159     * This means active content is allowed, though the default presentation must be *show everything* for balloting purposes
160     * Active content is allowed 
161     */
162    IG_PUBLISHER
163  }
164  
165  public enum StructureDefinitionRendererMode {
166    SUMMARY, // 5 cells: tree/name | flags | cardinality | type | details
167    BINDINGS, // tree/name + column for each kind of binding found, cells are lists of bindings 
168    OBLIGATIONS, // tree/name + column for each actor that has obligations
169    MAPPINGS, // tree/name + column for each other structure definition there is mappings for
170    DATA_DICT,  // detailed element view 
171  }
172
173  public enum ExampleScenarioRendererMode {
174    /**
175     * A visual presentation of the questionnaire, with a set of property panes that can be toggled on and off.
176     * Note that this is not the same as how the questionnaire would like on a form filler, since all dynamic behavior is ignored
177     */
178    ACTORS,
179
180    /**
181     * A table listing all the instances (and versions there-of) used in a scenario
182     */
183    INSTANCES,
184
185    /**
186     * Information about the processes (and sub-processes) defined in a scenario
187     */
188    PROCESSES,
189
190    /**
191     * A diagram showing interactions between the actors as part of the process steps
192     */
193    DIAGRAM
194  }
195
196  public enum QuestionnaireRendererMode {
197    /**
198     * A visual presentation of the questionnaire, with a set of property panes that can be toggled on and off.
199     * Note that this is not the same as how the questionnaire would like on a form filler, since all dynamic behavior is ignored
200     */
201    FORM,
202
203    /**
204     * a structured tree that presents the content of the questionnaire in a logical fashion
205     */
206    TREE,   
207
208    /**
209     * A structured tree that presents the enableWhen, terminology and expression bindings for the questionnaire 
210     */
211    LOGIC,
212
213    /**
214     * A presentation that lists all the items, with full details about them 
215     */
216    DEFNS, 
217
218    /**
219     * Rendered links to various openly available Form Filler applications that know how to render a questionnaire published in a package 
220     */
221    LINKS
222  }
223
224
225  public enum KnownLinkType {
226    SELF,  // absolute link to where the content is to be found (only used in a few circumstances when making external references to tools)
227    SPEC,  // version specific link to core specification
228    JSON_NAMES
229    
230  }
231  
232  public enum FixedValueFormat {
233    JSON, JSON_ALL, XML, XML_ALL;
234
235    public static FixedValueFormat fromCode(String value) {
236      if (value == null) {
237        return JSON;
238      }
239      switch (value.toLowerCase()) {
240      case "json" : return JSON;
241      case "json-all" : return JSON_ALL;
242      case "xml" : return XML;
243      case "xml-all" : return XML_ALL;
244      }
245      return JSON;
246    }
247
248    public boolean notPrimitives() {
249      return this == JSON || this == XML;
250    }
251
252    public boolean isXml() {
253      return this == XML_ALL || this == XML;
254    }
255  }
256
257  public enum MultiLanguagePolicy {
258    NONE, // ONLY render the language in the locale
259    DESIGNATIONS,  // in addition to the locale language, render designations from other languages (eg. as found in code systems and value sets
260    ALL // in addition to translations in designations, look for an render translations (WIP)
261  }
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278  private final IWorkerContext worker;
279  private MarkDownProcessor markdown;
280  private ResourceRendererMode mode;
281  private GenerationRules rules;
282  private IReferenceResolver resolver;
283  private ILiquidTemplateProvider templateProvider;
284  private IHostApplicationServices services;
285  private ITypeParser parser;
286
287  // i18n related fields
288  private boolean secondaryLang; // true if this is not the primary language for the resource
289  private MultiLanguagePolicy multiLanguagePolicy = MultiLanguagePolicy.NONE;
290  private Set<String> allowedLanguages = new HashSet<>(); 
291  private ZoneId timeZoneId;
292  private DateTimeFormatter dateTimeFormat;
293  private DateTimeFormatter dateFormat;
294  private DateTimeFormatter dateYearFormat;
295  private DateTimeFormatter dateYearMonthFormat;
296  
297  private String localPrefix; // relative link within local context
298  private int headerLevelContext;
299  private boolean canonicalUrlsAsLinks;
300  private boolean pretty;
301  private boolean showSummaryTable; // for canonical resources
302  private boolean contained;
303  private boolean oids;
304
305
306  private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5);
307  private boolean noSlowLookup;
308  private List<String> codeSystemPropList = new ArrayList<>();
309
310  private ProfileUtilities profileUtilitiesR;
311  private ContextUtilities contextUtilities;
312  private String definitionsTarget;
313  private String destDir;
314  private boolean inlineGraphics;
315  private StandardsStatus defaultStandardsStatus;
316
317  private ExampleScenarioRendererMode scenarioMode = null;
318  private QuestionnaireRendererMode questionnaireMode = QuestionnaireRendererMode.FORM;
319  private StructureDefinitionRendererMode structureMode = StructureDefinitionRendererMode.SUMMARY;
320  private FixedValueFormat fixedFormat = FixedValueFormat.JSON;
321  
322  private boolean showComments = false;
323
324  private FhirPublication targetVersion;
325  private boolean copyButton;
326  private ProfileKnowledgeProvider pkp;
327  private String changeVersion;
328  private List<String> files = new ArrayList<String>(); // files created as by-products in destDir
329  
330  private Map<KnownLinkType, String> links = new HashMap<>();
331  private Map<String, StringPair> namedLinks = new HashMap<>();
332  private boolean addName = false;
333  private Map<String, String> typeMap = new HashMap<>(); // type aliases that can be resolved in Markdown type links (mainly for cross-version usage)
334  private int base64Limit = 1024;
335  private boolean shortPatientForm;
336  private String uniqueLocalPrefix;
337  private Set<String> anchors = new HashSet<>();
338  private boolean unknownLocalReferencesNotLinks;
339  private IResourceLinkResolver resolveLinkResolver;
340  private boolean debug;
341  private DesignationMode designationMode;
342  private boolean noHeader;
343  private Set<ActorDefinition> actorWhiteList = new HashSet<>();
344  private boolean trackNarrativeSource;
345  private KeyIssuer crossLinkKeyGen;
346  private int randomTracker;
347  private boolean testing;
348  private PackageInformation pi;
349  @Getter @Setter boolean showStandardsStatus;
350
351  /**
352   * 
353   * @param workerContext - access to all related resources that might be needed
354   * @param pi - package information needed for certain Questionnaire rendering views
355   * @param markdown - appropriate markdown processing engine 
356   * @param terminologyServiceOptions - options to use when looking up codes
357   * @param specLink - path to FHIR specification
358   * @param locale - i18n for rendering
359   */
360  public RenderingContext(IWorkerContext workerContext, PackageInformation pi, MarkDownProcessor markdown, ValidationOptions terminologyServiceOptions, String specLink, String localPrefix, Locale locale, ResourceRendererMode mode, GenerationRules rules) {
361    super();
362    this.worker = workerContext;
363    this.pi = pi;
364    this.markdown = markdown;
365    this.setLocale(locale);
366    this.links.put(KnownLinkType.SPEC, specLink);
367    this.localPrefix = localPrefix;
368    this.mode = mode;
369    this.rules = rules;
370    this.designationMode = DesignationMode.ALL;
371    if (terminologyServiceOptions != null) {
372      this.terminologyServiceOptions = terminologyServiceOptions;
373    }
374    crossLinkKeyGen = new KeyIssuer("xn");
375  }
376
377  public RenderingContext(IWorkerContext workerContext, MarkDownProcessor markdown, ValidationOptions terminologyServiceOptions, String specLink, String localPrefix, Locale locale, ResourceRendererMode mode, GenerationRules rules) {
378    this(workerContext, null, markdown, terminologyServiceOptions, specLink, localPrefix, locale, mode, rules);
379  }
380  
381  public RenderingContext copy(boolean copyAnchors) {
382    RenderingContext res = new RenderingContext(worker, pi, markdown, terminologyServiceOptions, getLink(KnownLinkType.SPEC, false), localPrefix, getLocale(), mode, rules);
383
384    res.resolver = resolver;
385    res.templateProvider = templateProvider;
386    res.services = services;
387    res.parser = parser;
388
389    res.headerLevelContext = headerLevelContext;
390    res.canonicalUrlsAsLinks = canonicalUrlsAsLinks;
391    res.pretty = pretty;
392    res.contained = contained;
393    
394    res.noSlowLookup = noSlowLookup;
395    res.codeSystemPropList.addAll(codeSystemPropList);
396
397    res.profileUtilitiesR = profileUtilitiesR;
398    res.contextUtilities = contextUtilities;
399    res.definitionsTarget = definitionsTarget;
400    res.destDir = destDir;
401    res.scenarioMode = scenarioMode;
402    res.questionnaireMode = questionnaireMode;
403    res.structureMode = structureMode;
404    res.showSummaryTable = showSummaryTable;
405    res.links.putAll(links);
406    res.inlineGraphics = inlineGraphics;
407    res.timeZoneId = timeZoneId;
408    res.dateTimeFormat = dateTimeFormat;
409    res.dateFormat = dateFormat;
410    res.dateYearFormat = dateYearFormat;
411    res.dateYearMonthFormat = dateYearMonthFormat;
412    res.targetVersion = targetVersion;
413    res.showComments = showComments;
414    res.copyButton = copyButton;
415    res.pkp = pkp;
416    res.defaultStandardsStatus = defaultStandardsStatus;
417    res.changeVersion = changeVersion;
418
419    res.terminologyServiceOptions = terminologyServiceOptions.copy();
420    res.typeMap.putAll(typeMap);
421    res.multiLanguagePolicy = multiLanguagePolicy;
422    res.allowedLanguages.addAll(allowedLanguages);
423    if (copyAnchors) {
424       res.anchors = anchors;
425    }
426    res.unknownLocalReferencesNotLinks = unknownLocalReferencesNotLinks;
427    res.resolveLinkResolver = resolveLinkResolver;
428    res.debug = debug;
429    res.noHeader = noHeader;
430    res.uniqueLocalPrefix = uniqueLocalPrefix;
431    res.secondaryLang = secondaryLang;
432    res.fixedFormat = fixedFormat;
433    res.oids = oids;
434    res.base64Limit = base64Limit;
435    res.shortPatientForm = shortPatientForm;
436    res.designationMode = designationMode;
437    res.addName = addName;
438    res.typeMap = typeMap;
439    res.trackNarrativeSource = trackNarrativeSource;
440    res.crossLinkKeyGen = crossLinkKeyGen;
441    
442    res.getActorWhiteList().addAll(actorWhiteList);
443
444// not sure about these    
445//    private List<String> files = new ArrayList<String>(); // files created as by-products in destDir
446//    private Map<KnownLinkType, String> links = new HashMap<>();
447//    private Map<String, StringPair> namedLinks = new HashMap<>();
448
449    return res;
450  }
451  
452
453  public IWorkerContext getContext() {
454    return worker;
455  }
456
457  public PackageInformation getPackageInformation() {
458    return pi;
459  }
460  
461  // -- 2. Markdown support -------------------------------------------------------
462
463  public ProfileUtilities getProfileUtilities() {
464    if (profileUtilitiesR == null) {
465      profileUtilitiesR = new ProfileUtilities(worker, null, pkp);
466    }
467    return profileUtilitiesR;
468  }
469
470  public IWorkerContext getWorker() {
471    return worker;
472  }
473
474  public boolean isCanonicalUrlsAsLinks() {
475    return canonicalUrlsAsLinks;
476  }
477
478  public RenderingContext setCanonicalUrlsAsLinks(boolean canonicalUrlsAsLinks) {
479    this.canonicalUrlsAsLinks = canonicalUrlsAsLinks;
480    return this;
481  }
482
483  public MarkDownProcessor getMarkdown() {
484    if (markdown == null) {
485      markdown = new MarkDownProcessor(Dialect.COMMON_MARK);
486    }
487    return markdown;
488  }
489
490  public MultiLanguagePolicy getMultiLanguagePolicy() {
491    return multiLanguagePolicy;
492  }
493
494  public void setMultiLanguagePolicy(MultiLanguagePolicy multiLanguagePolicy) {
495    this.multiLanguagePolicy = multiLanguagePolicy;
496  }
497
498  public Set<String> getAllowedLanguages() {
499    return allowedLanguages;
500  }
501
502  public String getLocalPrefix() {
503    return localPrefix;
504  }
505
506  public ValidationOptions getTerminologyServiceOptions() {
507    return terminologyServiceOptions;
508  }
509
510  public int getHeaderLevelContext() {
511    return headerLevelContext;
512  }
513
514  public RenderingContext setHeaderLevelContext(int headerLevelContext) {
515    this.headerLevelContext = headerLevelContext;
516    return this;
517  }
518
519  public IReferenceResolver getResolver() {
520    return resolver;
521  }
522
523  public RenderingContext setResolver(IReferenceResolver resolver) {
524    this.resolver = resolver;
525    return this;
526  }
527
528  public RenderingContext setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
529    this.terminologyServiceOptions = terminologyServiceOptions;
530    return this;
531  }
532
533  public boolean isNoSlowLookup() {
534    return noSlowLookup;
535  }
536
537  public RenderingContext setNoSlowLookup(boolean noSlowLookup) {
538    this.noSlowLookup = noSlowLookup;
539    return this;
540  }
541
542  public String getDefinitionsTarget() {
543    return definitionsTarget;
544  }
545
546  public RenderingContext setDefinitionsTarget(String definitionsTarget) {
547    this.definitionsTarget = definitionsTarget;
548    return this;
549  }
550
551  public String getDestDir() {
552    return destDir;
553  }
554
555  public RenderingContext setDestDir(String destDir) {
556    this.destDir = destDir;
557    return this;
558  }
559
560  public RenderingContext setProfileUtilities(ProfileUtilities profileUtilities) {
561    this.profileUtilitiesR = profileUtilities;
562    if (pkp == null && profileUtilities.getPkp() != null) {
563      pkp = profileUtilities.getPkp();
564    }
565    return this;
566  }
567
568  public ILiquidTemplateProvider getTemplateProvider() {
569    return templateProvider;
570  }
571
572  public RenderingContext setTemplateProvider(ILiquidTemplateProvider templateProvider) {
573    this.templateProvider = templateProvider;
574    return this;
575  }
576
577  public IHostApplicationServices getServices() {
578    return services;
579  }
580
581  public RenderingContext setServices(IHostApplicationServices services) {
582    this.services = services;
583    return this;
584  }
585
586  public boolean isPretty() {
587    return pretty;
588  }
589
590  public RenderingContext setPretty(boolean pretty) {
591    this.pretty = pretty;
592    return this;
593  }
594
595  public ITypeParser getParser() {
596    return parser;
597  }
598
599  public RenderingContext setParser(ITypeParser parser) {
600    this.parser = parser;
601    return this;
602  }
603
604
605  public List<String> getCodeSystemPropList() {
606    return codeSystemPropList;
607  }
608
609  public RenderingContext setCodeSystemPropList(List<String> codeSystemPropList) {
610    this.codeSystemPropList = codeSystemPropList;
611    return this;
612  }
613
614
615  public boolean isInlineGraphics() {
616    return inlineGraphics;
617  }
618
619  public RenderingContext setInlineGraphics(boolean inlineGraphics) {
620    this.inlineGraphics = inlineGraphics;
621    return this;
622  }
623
624  public boolean isShowSummaryTable() {
625    return showSummaryTable;
626  }
627
628  public RenderingContext setShowSummaryTable(boolean header) {
629    this.showSummaryTable = header;
630    return this;
631  }
632
633  public ExampleScenarioRendererMode getScenarioMode() {
634    return scenarioMode;
635  }
636
637  public RenderingContext setScenarioMode(ExampleScenarioRendererMode scenarioMode) {
638    this.scenarioMode = scenarioMode;
639    return this;
640  }
641
642  public QuestionnaireRendererMode getQuestionnaireMode() {
643    return questionnaireMode;
644  }
645
646  public RenderingContext setQuestionnaireMode(QuestionnaireRendererMode questionnaireMode) {
647    this.questionnaireMode = questionnaireMode;
648    return this;
649  }
650  
651  public StructureDefinitionRendererMode getStructureMode() {
652    return structureMode;
653  }
654
655  public RenderingContext setStructureMode(StructureDefinitionRendererMode structureMode) {
656    this.structureMode = structureMode;
657    return this;
658  }
659
660  public String fixReference(String ref) {
661    if (ref == null) {
662      return null;
663    }
664    if (!Utilities.isAbsoluteUrl(ref)) {
665      return (localPrefix == null ? "" : localPrefix)+ref;
666    }
667    if (ref.startsWith("http://hl7.org/fhir") && !ref.substring(20).contains("/")) {
668      return getLink(KnownLinkType.SPEC, true)+ref.substring(20);
669    }
670    return ref;
671  }
672
673  public RenderingContext setLocalPrefix(String localPrefix) {
674    this.localPrefix = localPrefix;
675    return this;
676  }
677
678  public FhirPublication getTargetVersion() {
679    return targetVersion;
680  }
681
682  public RenderingContext setTargetVersion(FhirPublication targetVersion) {
683    this.targetVersion = targetVersion;
684    return this;
685  }
686
687  public boolean isTechnicalMode() {
688    return mode == ResourceRendererMode.TECHNICAL;
689  }
690
691  /**
692   * if the timezone is null, the rendering will default to the source timezone
693   * in the resource
694   * 
695   * Note that if you're working server side, the FHIR project recommends the use
696   * of the Date header so that clients know what timezone the server defaults to,
697   * 
698   * There is no standard way for the server to know what the client timezone is. 
699   * In the case where the client timezone is unknown, the timezone should be null
700   *
701   * @return the specified timezone to render in
702   */
703  public ZoneId getTimeZoneId() {
704    return timeZoneId;
705  }
706
707  public RenderingContext setTimeZoneId(ZoneId timeZoneId) {
708    this.timeZoneId = timeZoneId;
709    return this;
710  }
711
712
713  /**
714   * In the absence of a specified format, the renderers will default to 
715   * the FormatStyle.MEDIUM for the current locale.
716   * 
717   * @return the format to use
718   */
719  public DateTimeFormatter getDateTimeFormat() {
720    return this.dateTimeFormat;
721  }
722
723  public RenderingContext setDateTimeFormat(DateTimeFormatter dateTimeFormat) {
724    this.dateTimeFormat = dateTimeFormat;
725    return this;
726  }
727
728  public RenderingContext setDateTimeFormatString(String dateTimeFormat) {
729    this.dateTimeFormat = DateTimeFormatter.ofPattern(dateTimeFormat);
730    return this;
731  }
732
733  /**
734   * In the absence of a specified format, the renderers will default to 
735   * the FormatStyle.MEDIUM for the current locale.
736   * 
737   * @return the format to use
738   */
739  public DateTimeFormatter getDateFormat() {
740    return this.dateFormat;
741  }
742
743  public RenderingContext setDateFormat(DateTimeFormatter dateFormat) {
744    this.dateFormat = dateFormat;
745    return this;
746  }
747
748  public RenderingContext setDateFormatString(String dateFormat) {
749    this.dateFormat = DateTimeFormatter.ofPattern(dateFormat);
750    return this;
751  }
752
753  public DateTimeFormatter getDateYearFormat() {
754    return dateYearFormat;
755  }
756
757  public RenderingContext setDateYearFormat(DateTimeFormatter dateYearFormat) {
758    this.dateYearFormat = dateYearFormat;
759    return this;
760  }
761
762  public RenderingContext setDateYearFormatString(String dateYearFormat) {
763    this.dateYearFormat = DateTimeFormatter.ofPattern(dateYearFormat);
764    return this;
765  }
766
767  public DateTimeFormatter getDateYearMonthFormat() {
768    return dateYearMonthFormat;
769  }
770
771  public RenderingContext setDateYearMonthFormat(DateTimeFormatter dateYearMonthFormat) {
772    this.dateYearMonthFormat = dateYearMonthFormat;
773    return this;
774  }
775
776  public RenderingContext setDateYearMonthFormatString(String dateYearMonthFormat) {
777    this.dateYearMonthFormat = DateTimeFormatter.ofPattern(dateYearMonthFormat);
778    return this;
779  }
780
781  public ResourceRendererMode getMode() {
782    return mode;
783  }
784
785  public RenderingContext setMode(ResourceRendererMode mode) {
786    this.mode = mode;
787    return this;
788  }
789
790  public boolean isContained() {
791    return contained;
792  }
793
794  public RenderingContext setContained(boolean contained) {
795    this.contained = contained;
796    return this;
797  }
798  public boolean isShowComments() {
799    return showComments;
800  }
801  public RenderingContext setShowComments(boolean showComments) {
802    this.showComments = showComments;
803    return this;
804  }
805  public boolean isCopyButton() {
806    return copyButton;
807  }
808  public RenderingContext setCopyButton(boolean copyButton) {
809    this.copyButton = copyButton;
810    return this;
811  }
812  
813  public RenderingContext setPkp(ProfileKnowledgeProvider pkp) {
814    this.pkp = pkp;
815    return this;
816  }
817  public ProfileKnowledgeProvider getPkp() {
818    return pkp;
819  }
820  
821  public boolean hasLink(KnownLinkType link) {
822    return links.containsKey(link);
823  }
824  
825  public String getLink(KnownLinkType link, boolean secure) {
826    String url = links.get(link);
827    if (url != null && secure && url.startsWith("http://")) {
828      url = "https://" + url.substring("http://".length());
829    }
830    return url;
831  }
832  public void addLink(KnownLinkType type, String link) {
833    links.put(type, link);
834    
835  }
836  public GenerationRules getRules() {
837    return rules;
838  }
839  public RenderingContext setRules(GenerationRules rules) {
840    this.rules = rules;
841    return this;
842  }
843  public StandardsStatus getDefaultStandardsStatus() {
844    return defaultStandardsStatus;
845  }
846  public RenderingContext setDefaultStandardsStatus(StandardsStatus defaultStandardsStatus) {
847    this.defaultStandardsStatus = defaultStandardsStatus;
848    return this;
849  }
850
851  public String getChangeVersion() {
852    return changeVersion;
853  }
854
855  public RenderingContext setChangeVersion(String changeVersion) {
856    this.changeVersion = changeVersion;
857    return this;
858  }
859
860  public Map<String, StringPair> getNamedLinks() {
861    return namedLinks;
862  }
863
864  public void registerFile(String n) {
865    try {
866      files.add(Utilities.path(destDir, n));
867    } catch (IOException e) {
868    }
869  }
870
871  public List<String> getFiles() {
872    return files;
873  }
874
875  public FixedValueFormat getFixedFormat() {
876    return fixedFormat;
877  }
878
879  public void setFixedFormat(FixedValueFormat fixedFormat) {
880    this.fixedFormat = fixedFormat;
881  }
882
883  public boolean isAddName() {
884    return addName;
885  }
886
887  public RenderingContext setAddName(boolean addName) {
888    this.addName = addName;
889    return this;
890  }
891
892  public Map<String, String> getTypeMap() {
893    return typeMap;
894  }
895
896
897  public String toStr(int v) {
898    NumberFormat nf = NumberFormat.getInstance(getLocale());
899    return nf.format(v);
900  }
901
902
903  public String getTranslated(PrimitiveType<?>... tl) {
904    for (PrimitiveType<?> t : tl) {
905      StringType v = ExtensionUtilities.getLanguageTranslationElement(t, getLocale().toLanguageTag());
906      if (v != null) {
907        return v.primitiveValue();
908      }
909    }
910    for (PrimitiveType<?> t : tl) {
911      if (t.hasValue()) {
912        return t.primitiveValue();
913      }
914    }
915    return null;
916  }
917
918  public String getTranslated(ResourceWrapper t) {
919    if (t == null) {
920      return null;
921    }
922
923      for (ResourceWrapper e : t.extensions(ExtensionDefinitions.EXT_TRANSLATION)) {
924        String l = e.extensionString("lang");
925        if (l != null && l.equals(getLocale().toLanguageTag())) {
926          String v = e.extensionString("content");
927          if (v != null) {
928            return v;
929          }
930        }
931      }
932
933    return t.primitiveValue();
934  }
935
936  public StringType getTranslatedElement(PrimitiveType<?>... tl) {
937    for (PrimitiveType<?> t : tl) {
938      StringType v = ExtensionUtilities.getLanguageTranslationElement(t, getLocale().toLanguageTag());
939      if (v != null) {
940        return v;
941      }
942    }
943    for (PrimitiveType<?> t : tl) {
944      if (t.hasValue()) {
945        if (t instanceof StringType) {
946          return (StringType) t;
947        } else {
948          return new StringType(t.asStringValue());
949        }
950      }
951    }
952    return null;
953  }
954
955
956  public String getTranslatedCode(Base b, String codeSystem) {
957    if (b instanceof org.hl7.fhir.r5.model.Element) {
958      org.hl7.fhir.r5.model.Element e = (org.hl7.fhir.r5.model.Element) b;
959      String v = ExtensionUtilities.getLanguageTranslation(e, getLocale().toLanguageTag());
960      if (v != null) {
961        return v;
962      }
963      // no? then see if the tx service can translate it for us
964      try {
965        ValidationResult t = getContext().validateCode(getTerminologyServiceOptions().withLanguage(getLocale().toLanguageTag()).withVersionFlexible(true),
966          codeSystem, null, e.primitiveValue(), null);
967        if (t.isOk() && t.getDisplay() != null) {
968          return t.getDisplay();
969        }
970      } catch (Exception ex) {
971        // Do nothing.
972      }
973      if (e instanceof Enumeration<?>) {
974        return ((Enumeration<?>) e).getDisplay();
975      } else {
976        return e.primitiveValue();
977      }
978    } else if (b instanceof Element) {
979      return getTranslatedCode((Element) b, codeSystem);
980    } else {
981      return "??";
982    }
983  }
984
985  public String getTranslatedCode(String code, String codeSystem) {
986      try {
987        ValidationResult t = getContext().validateCode(getTerminologyServiceOptions().withLanguage(getLocale().toLanguageTag()).withVersionFlexible(true), codeSystem, null, code, null);
988        if (t.isOk() && t.getDisplay() != null) {
989          return t.getDisplay();
990        } else {
991          return code;
992        }
993      } catch (Exception ex) {
994        return code;
995      }
996  }
997  
998  public String getTranslatedCode(Enumeration<?> e, String codeSystem) {
999    String v = ExtensionUtilities.getLanguageTranslation(e, getLocale().toLanguageTag());
1000    if (v != null) {
1001      return v;
1002    }
1003    // no? then see if the tx service can translate it for us
1004    try {
1005      ValidationResult t = getContext().validateCode(getTerminologyServiceOptions().withLanguage(getLocale().toLanguageTag()).withVersionFlexible(true),
1006        codeSystem, null, e.getCode(), null);
1007      if (t.isOk() && t.getDisplay() != null) {
1008        return t.getDisplay();
1009      }
1010    } catch (Exception ex) {
1011      // nothing
1012    }
1013
1014    try {
1015      ValidationResult t = getContext().validateCode(getTerminologyServiceOptions().withVersionFlexible(true),
1016        codeSystem, null, e.getCode(), null);
1017      if (t.isOk() && t.getDisplay() != null) {
1018        return t.getDisplay();
1019      }
1020    } catch (Exception ex) {
1021      // nothing
1022    }
1023
1024    return e.getCode();
1025  }
1026  
1027  public String getTranslatedCode(Element e, String codeSystem) {
1028
1029      // first we look through the translation extensions
1030      for (Element ext : e.getChildrenByName("extension")) {
1031        String url = ext.getNamedChildValue("url");
1032        if (url.equals(ExtensionDefinitions.EXT_TRANSLATION)) {
1033          Base e1 = ext.getExtensionValue("lang");
1034
1035          if (e1 != null && e1.primitiveValue() != null && e1.primitiveValue().equals(getLocale().toLanguageTag())) {
1036            e1 = ext.getExtensionValue("content");
1037            if (e1 != null && e1.isPrimitive()) {
1038              return e1.primitiveValue();
1039            }
1040          }
1041        }
1042
1043      // no? then see if the tx service can translate it for us 
1044      try {
1045        ValidationResult t = getContext().validateCode(getTerminologyServiceOptions().withLanguage(getLocale().toLanguageTag()).withVersionFlexible(true),
1046            codeSystem, null, e.primitiveValue(), null);
1047        if (t.isOk() && t.getDisplay() != null) {
1048          return t.getDisplay();
1049        }
1050      } catch (Exception ex) {
1051        // nothing
1052      }
1053    }
1054    return e.primitiveValue();
1055  }
1056
1057  public RenderingContext withLocale(Locale locale) {
1058    setLocale(locale);
1059    return this;
1060  }
1061
1062  public RenderingContext withLocaleCode(String locale) {
1063    setLocale(Locale.forLanguageTag(locale));
1064    return this;
1065  }
1066
1067  public RenderingContext withMode(ResourceRendererMode mode) {
1068    setMode(mode);
1069    return this;
1070  }
1071  
1072  public ContextUtilities getContextUtilities() {
1073    if (contextUtilities == null) {
1074      contextUtilities = new ContextUtilities(worker);
1075    }
1076    return contextUtilities;
1077  }
1078
1079  public int getBase64Limit() {
1080    return base64Limit;
1081  }
1082
1083  public void setBase64Limit(int base64Limit) {
1084    this.base64Limit = base64Limit;
1085  }
1086
1087  public boolean isShortPatientForm() {
1088    return shortPatientForm;
1089  }
1090
1091  public void setShortPatientForm(boolean shortPatientForm) {
1092    this.shortPatientForm = shortPatientForm;
1093  }
1094
1095  public boolean isSecondaryLang() {
1096    return secondaryLang;
1097  }
1098
1099  public void setSecondaryLang(boolean secondaryLang) {
1100    this.secondaryLang = secondaryLang;
1101  }
1102
1103  public String prefixAnchor(String anchor) {
1104    return uniqueLocalPrefix == null ? anchor : uniqueLocalPrefix+"-" + anchor;
1105  }
1106
1107  public String prefixLocalHref(String url) {
1108    if (url == null || uniqueLocalPrefix == null || !url.startsWith("#")) {
1109      return url;
1110    }
1111    return "#"+uniqueLocalPrefix+"-"+url.substring(1);
1112  }
1113
1114  public String getUniqueLocalPrefix() {
1115    return uniqueLocalPrefix;
1116  }
1117
1118  public void setUniqueLocalPrefix(String uniqueLocalPrefix) {
1119    this.uniqueLocalPrefix = uniqueLocalPrefix;
1120  }
1121
1122  public RenderingContext withUniqueLocalPrefix(String uniqueLocalPrefix) {
1123    RenderingContext self = this.copy(true);
1124    self.uniqueLocalPrefix = uniqueLocalPrefix;
1125    return self;
1126  }
1127
1128  public RenderingContext forContained() {
1129    RenderingContext self = this.copy(true);
1130    self.contained = true;
1131    return self;
1132  }
1133  
1134  public boolean hasAnchor(String anchor) {
1135    return anchors.contains(anchor);
1136  }
1137  
1138  public void addAnchor(String anchor) {
1139    anchors.add(anchor);
1140  }
1141
1142  public Set<String> getAnchors() {
1143    return anchors;
1144  }
1145
1146  public void clearAnchors() {
1147    anchors.clear();
1148  }
1149
1150  public boolean isUnknownLocalReferencesNotLinks() {
1151    return unknownLocalReferencesNotLinks;
1152  }
1153
1154  public void setUnknownLocalReferencesNotLinks(boolean unknownLocalReferencesNotLinks) {
1155    this.unknownLocalReferencesNotLinks = unknownLocalReferencesNotLinks;
1156  }
1157  
1158  public <T extends Resource> T findLinkableResource(Class<T> class_, String uri) throws IOException {
1159    if (resolveLinkResolver == null) {
1160      return null;          
1161    } else {
1162      return resolveLinkResolver.findLinkableResource(class_, uri);
1163    }
1164  }
1165
1166  public IResourceLinkResolver getResolveLinkResolver() {
1167    return resolveLinkResolver;
1168  }
1169
1170  public void setResolveLinkResolver(IResourceLinkResolver resolveLinkResolver) {
1171    this.resolveLinkResolver = resolveLinkResolver;
1172  }
1173
1174  public boolean isDebug() {
1175    return debug;
1176  }
1177
1178  public void setDebug(boolean debug) {
1179    this.debug = debug;
1180  }
1181
1182  public DesignationMode getDesignationMode() {
1183    return designationMode;
1184  }
1185
1186  public void setDesignationMode(DesignationMode designationMode) {
1187    this.designationMode = designationMode;
1188  }
1189
1190  public boolean isOids() {
1191    return oids;
1192  }
1193
1194  public void setOids(boolean oids) {
1195    this.oids = oids;
1196  }
1197
1198  public RenderingContext withOids(boolean oids) {
1199    RenderingContext self = this.copy(false);
1200    self.oids = oids;
1201    return self;
1202  }
1203
1204  public boolean isNoHeader() {
1205    return noHeader;
1206  }
1207
1208  public void setNoHeader(boolean noHeader) {
1209    this.noHeader = noHeader;
1210  }
1211
1212  public Set<ActorDefinition> getActorWhiteList() {
1213    return actorWhiteList;
1214  }
1215
1216  public boolean isTrackNarrativeSource() {
1217    return trackNarrativeSource;
1218  }
1219
1220  public void setTrackNarrativeSource(boolean trackNarrativeSource) {
1221    this.trackNarrativeSource = trackNarrativeSource;
1222  }
1223
1224  public String nextXNKey() {
1225    return crossLinkKeyGen.issueKey();
1226  }
1227
1228  public String getRandomName(String id) {
1229    if (testing) {
1230      return id+"-"+(++randomTracker);
1231    } else {
1232      return UUID.randomUUID().toString().toLowerCase();
1233    }
1234  }
1235
1236  public boolean isTesting() {
1237    return testing;
1238  }
1239
1240  /**
1241   * testing is used to turn off production of random UUIDs and produce something known and predictable but
1242   * likely to produce name clashes in production - for the sake of test case reproducibility
1243   * @param testing
1244   */
1245  public void setTesting(boolean testing) {
1246    this.testing = testing;
1247  }
1248
1249  public boolean forValidResource() {
1250    return getRules() == GenerationRules.VALID_RESOURCE;
1251  }
1252
1253  public boolean forPublisher() {
1254    return getRules() == GenerationRules.IG_PUBLISHER;
1255  }
1256
1257
1258}