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