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