001package org.hl7.fhir.r5.renderers.utils; 002 003import java.io.IOException; 004import java.text.DateFormat; 005import java.time.ZoneId; 006import java.time.format.DateTimeFormatter; 007import java.time.format.FormatStyle; 008import java.util.ArrayList; 009import java.util.List; 010import java.util.Locale; 011import java.util.TimeZone; 012 013import org.hl7.fhir.exceptions.FHIRException; 014import org.hl7.fhir.exceptions.FHIRFormatError; 015import org.hl7.fhir.r5.conformance.ProfileUtilities; 016import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider; 017import org.hl7.fhir.r5.context.IWorkerContext; 018import org.hl7.fhir.r5.model.Base; 019import org.hl7.fhir.r5.model.DomainResource; 020import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 021import org.hl7.fhir.r5.model.FhirPublication; 022import org.hl7.fhir.r5.renderers.utils.Resolver.IReferenceResolver; 023import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 024import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; 025import org.hl7.fhir.utilities.MarkDownProcessor; 026import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; 027import org.hl7.fhir.utilities.Utilities; 028import org.hl7.fhir.utilities.validation.ValidationOptions; 029 030public class RenderingContext { 031 032 // provides liquid templates, if they are available for the content 033 public interface ILiquidTemplateProvider { 034 String findTemplate(RenderingContext rcontext, DomainResource r); 035 String findTemplate(RenderingContext rcontext, String resourceName); 036 } 037 038 // parses xml to an XML instance. Whatever codes provides this needs to provide something that parses the right version 039 public interface ITypeParser { 040 Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException ; 041 } 042 043 /** 044 * What kind of user the renderer is targeting - end users, or technical users 045 * 046 * This affects the way codes and references are rendered 047 * 048 * @author graha 049 * 050 */ 051 public enum ResourceRendererMode { 052 /** 053 * The user has no interest in the contents of the FHIR resource, and just wants to see the data 054 * 055 */ 056 END_USER, 057 058 /** 059 * The user wants to see the resource, but a technical view so they can see what's going on with the content 060 */ 061 TECHNICAL 062 } 063 064 public enum QuestionnaireRendererMode { 065 /** 066 * A visual presentation of the questionnaire, with a set of property panes that can be toggled on and off. 067 * Note that this is not the same as how the questionnaire would like on a form filler, since all dynamic behavior is ignored 068 */ 069 FORM, 070 071 /** 072 * a structured tree that presents the content of the questionnaire in a logical fashion 073 */ 074 TREE, 075 076 /** 077 * A structured tree that presents the enableWhen, terminology and expression bindings for the questionnaire 078 */ 079 LOGIC, 080 081 /** 082 * A presentation that lists all the items, with full details about them 083 */ 084 DEFNS, 085 086 /** 087 * Rendered links to various openly available Form Filler applications that know how to render a questionnaire published in a package 088 */ 089 LINKS 090 } 091 092 private IWorkerContext worker; 093 private MarkDownProcessor markdown; 094 private ResourceRendererMode mode; 095 private IReferenceResolver resolver; 096 private ILiquidTemplateProvider templateProvider; 097 private IEvaluationContext services; 098 private ITypeParser parser; 099 100 private String lang; 101 private String localPrefix; // relative link within local context 102 private String specificationLink; 103 private String selfLink; // absolute link to where the content is to be found (only used in a few circumstances when making external references to tools) 104 private int headerLevelContext; 105 private boolean canonicalUrlsAsLinks; 106 private boolean pretty; 107 private boolean header; 108 109 private ValidationOptions terminologyServiceOptions = new ValidationOptions(); 110 private boolean noSlowLookup; 111 private String tooCostlyNoteEmpty; 112 private String tooCostlyNoteNotEmpty; 113 private String tooCostlyNoteEmptyDependent; 114 private String tooCostlyNoteNotEmptyDependent; 115 private List<String> codeSystemPropList = new ArrayList<>(); 116 117 private ProfileUtilities profileUtilities; 118 private String definitionsTarget; 119 private String destDir; 120 private boolean inlineGraphics; 121 122 private QuestionnaireRendererMode questionnaireMode = QuestionnaireRendererMode.FORM; 123 private boolean addGeneratedNarrativeHeader = true; 124 125 private FhirPublication targetVersion; 126 private Locale locale; 127 private ZoneId timeZoneId; 128 private DateTimeFormatter dateTimeFormat; 129 private DateTimeFormatter dateFormat; 130 131 /** 132 * 133 * @param context - access to all related resources that might be needed 134 * @param markdown - appropriate markdown processing engine 135 * @param terminologyServiceOptions - options to use when looking up codes 136 * @param specLink - path to FHIR specification 137 * @param lang - langauage to render in 138 */ 139 public RenderingContext(IWorkerContext worker, MarkDownProcessor markdown, ValidationOptions terminologyServiceOptions, String specLink, String localPrefix, String lang, ResourceRendererMode mode) { 140 super(); 141 this.worker = worker; 142 this.markdown = markdown; 143 this.lang = lang; 144 this.specificationLink = specLink; 145 this.localPrefix = localPrefix; 146 this.mode = mode; 147 if (terminologyServiceOptions != null) { 148 this.terminologyServiceOptions = terminologyServiceOptions; 149 } 150 // default to US locale - discussion here: https://github.com/hapifhir/org.hl7.fhir.core/issues/666 151 this.locale = new Locale.Builder().setLanguageTag("en-US").build(); 152 profileUtilities = new ProfileUtilities(worker, null, null); 153 } 154 155 public IWorkerContext getContext() { 156 return worker; 157 } 158 159 // -- 2. Markdown support ------------------------------------------------------- 160 161 public ProfileUtilities getProfileUtilities() { 162 return profileUtilities; 163 } 164 165 public IWorkerContext getWorker() { 166 return worker; 167 } 168 169 public boolean isCanonicalUrlsAsLinks() { 170 return canonicalUrlsAsLinks; 171 } 172 173 public RenderingContext setCanonicalUrlsAsLinks(boolean canonicalUrlsAsLinks) { 174 this.canonicalUrlsAsLinks = canonicalUrlsAsLinks; 175 return this; 176 } 177 178 public MarkDownProcessor getMarkdown() { 179 if (markdown == null) { 180 markdown = new MarkDownProcessor(Dialect.COMMON_MARK); 181 } 182 return markdown; 183 } 184 185 public String getLang() { 186 return lang; 187 } 188 189 public String getSpecificationLink() { 190 return specificationLink; 191 } 192 193 public String getLocalPrefix() { 194 return localPrefix; 195 } 196 197 public ValidationOptions getTerminologyServiceOptions() { 198 return terminologyServiceOptions; 199 } 200 201 202 public String getTooCostlyNoteEmpty() { 203 return tooCostlyNoteEmpty; 204 } 205 206 public RenderingContext setTooCostlyNoteEmpty(String tooCostlyNoteEmpty) { 207 this.tooCostlyNoteEmpty = tooCostlyNoteEmpty; 208 return this; 209 } 210 211 public String getTooCostlyNoteNotEmpty() { 212 return tooCostlyNoteNotEmpty; 213 } 214 215 public RenderingContext setTooCostlyNoteNotEmpty(String tooCostlyNoteNotEmpty) { 216 this.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty; 217 return this; 218 } 219 220 public String getTooCostlyNoteEmptyDependent() { 221 return tooCostlyNoteEmptyDependent; 222 } 223 224 public RenderingContext setTooCostlyNoteEmptyDependent(String tooCostlyNoteEmptyDependent) { 225 this.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent; 226 return this; 227 } 228 229 public String getTooCostlyNoteNotEmptyDependent() { 230 return tooCostlyNoteNotEmptyDependent; 231 } 232 233 public RenderingContext setTooCostlyNoteNotEmptyDependent(String tooCostlyNoteNotEmptyDependent) { 234 this.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent; 235 return this; 236 } 237 238 public int getHeaderLevelContext() { 239 return headerLevelContext; 240 } 241 242 public RenderingContext setHeaderLevelContext(int headerLevelContext) { 243 this.headerLevelContext = headerLevelContext; 244 return this; 245 } 246 247 public IReferenceResolver getResolver() { 248 return resolver; 249 } 250 251 public RenderingContext setResolver(IReferenceResolver resolver) { 252 this.resolver = resolver; 253 return this; 254 } 255 256 public RenderingContext setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { 257 this.terminologyServiceOptions = terminologyServiceOptions; 258 return this; 259 } 260 261 public boolean isNoSlowLookup() { 262 return noSlowLookup; 263 } 264 265 public RenderingContext setNoSlowLookup(boolean noSlowLookup) { 266 this.noSlowLookup = noSlowLookup; 267 return this; 268 } 269 270 public String getDefinitionsTarget() { 271 return definitionsTarget; 272 } 273 274 public RenderingContext setDefinitionsTarget(String definitionsTarget) { 275 this.definitionsTarget = definitionsTarget; 276 return this; 277 } 278 279 public String getDestDir() { 280 return destDir; 281 } 282 283 public RenderingContext setDestDir(String destDir) { 284 this.destDir = destDir; 285 return this; 286 } 287 288 public RenderingContext setProfileUtilities(ProfileUtilities profileUtilities) { 289 this.profileUtilities = profileUtilities; 290 return this; 291 } 292 293 public ILiquidTemplateProvider getTemplateProvider() { 294 return templateProvider; 295 } 296 297 public RenderingContext setTemplateProvider(ILiquidTemplateProvider templateProvider) { 298 this.templateProvider = templateProvider; 299 return this; 300 } 301 302 public IEvaluationContext getServices() { 303 return services; 304 } 305 306 public RenderingContext setServices(IEvaluationContext services) { 307 this.services = services; 308 return this; 309 } 310 311 public boolean isPretty() { 312 return pretty; 313 } 314 315 public RenderingContext setPretty(boolean pretty) { 316 this.pretty = pretty; 317 return this; 318 } 319 320 public ITypeParser getParser() { 321 return parser; 322 } 323 324 public RenderingContext setParser(ITypeParser parser) { 325 this.parser = parser; 326 return this; 327 } 328 329 330 public List<String> getCodeSystemPropList() { 331 return codeSystemPropList; 332 } 333 334 public RenderingContext setCodeSystemPropList(List<String> codeSystemPropList) { 335 this.codeSystemPropList = codeSystemPropList; 336 return this; 337 } 338 339 public RenderingContext copy() { 340 RenderingContext res = new RenderingContext(worker, markdown, terminologyServiceOptions, specificationLink, localPrefix, lang, mode); 341 342 res.resolver = resolver; 343 res.templateProvider = templateProvider; 344 res.services = services; 345 res.parser = parser; 346 347 res.headerLevelContext = headerLevelContext; 348 res.canonicalUrlsAsLinks = canonicalUrlsAsLinks; 349 res.pretty = pretty; 350 351 res.noSlowLookup = noSlowLookup; 352 res.tooCostlyNoteEmpty = tooCostlyNoteEmpty; 353 res.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty; 354 res.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent; 355 res.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent; 356 res.codeSystemPropList.addAll(codeSystemPropList); 357 358 res.profileUtilities = profileUtilities; 359 res.definitionsTarget = definitionsTarget; 360 res.destDir = destDir; 361 res.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader; 362 363 return res; 364 } 365 366 public boolean isInlineGraphics() { 367 return inlineGraphics; 368 } 369 370 public RenderingContext setInlineGraphics(boolean inlineGraphics) { 371 this.inlineGraphics = inlineGraphics; 372 return this; 373 } 374 375 public boolean isHeader() { 376 return header; 377 } 378 379 public RenderingContext setHeader(boolean header) { 380 this.header = header; 381 return this; 382 } 383 384 public QuestionnaireRendererMode getQuestionnaireMode() { 385 return questionnaireMode; 386 } 387 388 public RenderingContext setQuestionnaireMode(QuestionnaireRendererMode questionnaireMode) { 389 this.questionnaireMode = questionnaireMode; 390 return this; 391 } 392 393 public String getSelfLink() { 394 return selfLink; 395 } 396 397 public RenderingContext setSelfLink(String selfLink) { 398 this.selfLink = selfLink; 399 return this; 400 } 401 402 public String fixReference(String ref) { 403 if (!Utilities.isAbsoluteUrl(ref)) { 404 return (localPrefix == null ? "" : localPrefix)+ref; 405 } 406 if (ref.startsWith("http://hl7.org/fhir") && !ref.substring(20).contains("/")) { 407 return specificationLink+ref.substring(20); 408 } 409 return ref; 410 } 411 412 public RenderingContext setLang(String lang) { 413 this.lang = lang; 414 return this; 415 } 416 417 public RenderingContext setLocalPrefix(String localPrefix) { 418 this.localPrefix = localPrefix; 419 return this; 420 } 421 422 public boolean isAddGeneratedNarrativeHeader() { 423 return addGeneratedNarrativeHeader; 424 } 425 426 public RenderingContext setAddGeneratedNarrativeHeader(boolean addGeneratedNarrativeHeader) { 427 this.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader; 428 return this; 429 } 430 431 public FhirPublication getTargetVersion() { 432 return targetVersion; 433 } 434 435 public void setTargetVersion(FhirPublication targetVersion) { 436 this.targetVersion = targetVersion; 437 } 438 439 public boolean isTechnicalMode() { 440 return mode == ResourceRendererMode.TECHNICAL; 441 } 442 443 public boolean hasLocale() { 444 return locale != null; 445 } 446 447 public Locale getLocale() { 448 if (locale == null) { 449 return Locale.getDefault(); 450 } else { 451 return locale; 452 } 453 } 454 455 public void setLocale(Locale locale) { 456 this.locale = locale; 457 } 458 459 460 /** 461 * if the timezone is null, the rendering will default to the source timezone 462 * in the resource 463 * 464 * Note that if you're working server side, the FHIR project recommends the use 465 * of the Date header so that clients know what timezone the server defaults to, 466 * 467 * There is no standard way for the server to know what the client timezone is. 468 * In the case where the client timezone is unknown, the timezone should be null 469 * 470 * @return the specified timezone to render in 471 */ 472 public ZoneId getTimeZoneId() { 473 return timeZoneId; 474 } 475 476 public void setTimeZoneId(ZoneId timeZoneId) { 477 this.timeZoneId = timeZoneId; 478 } 479 480 481 /** 482 * In the absence of a specified format, the renderers will default to 483 * the FormatStyle.MEDIUM for the current locale. 484 * 485 * @return the format to use 486 */ 487 public DateTimeFormatter getDateTimeFormat() { 488 return this.dateTimeFormat; 489 } 490 491 public void setDateTimeFormat(DateTimeFormatter dateTimeFormat) { 492 this.dateTimeFormat = dateTimeFormat; 493 } 494 495 /** 496 * In the absence of a specified format, the renderers will default to 497 * the FormatStyle.MEDIUM for the current locale. 498 * 499 * @return the format to use 500 */ 501 public DateTimeFormatter getDateFormat() { 502 return this.dateFormat; 503 } 504 505 public void setDateFormat(DateTimeFormatter dateFormat) { 506 this.dateFormat = dateFormat; 507 } 508 509 public ResourceRendererMode getMode() { 510 return mode; 511 } 512 513 public void setMode(ResourceRendererMode mode) { 514 this.mode = mode; 515 } 516 517 518}