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