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