Purpose: Provides general purpose display of CDA release 2.0 and 2.1 (Specification: ANSI/HL7
CDAR2) and CDA release 3 (Specification was pulled after ballot) documents. It may
also be a starting point for people interested in extending the display. This stylesheet
displays all section content, but does not try to render each and every header attribute.
For header attributes it tries to be smart in displaying essentials, which is still
a lot.
License: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Warranty The CDA XSL is a sample rendering and should be used in that fashion without warranty
or guarantees of suitability for a particular purpose. The stylesheet should be tested
locally by implementers before production usage.
History: This stylesheet stands on the shoulders of giants. The stylesheet is the cumulative
work of several developers; the most significant prior milestones were the foundation
work from HL7 Germany and Finland (Tyylitiedosto) and HL7 US (Calvin Beebe), and the
presentation approach from Tony Schaller, medshare GmbH provided at IHIC 2009. The
stylesheet has subsequently been maintained/updated by Lantana Group (US) and Nictiz
(NL).
Revisions: The release notes previously contained in the stylesheet, have moved to the GitHub where the project is maintained.
<xsl:template match="hl7:component/hl7:nonXMLBody | hl7:observationMedia"><xsl:param name="usemap"/><xsl:variable name="renderID"><xsl:choose><xsl:when test="@ID"><xsl:value-of select="@ID"/></xsl:when><xsl:otherwise><xsl:value-of select="concat(generate-id(.), '_', local-name(.))"/></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="renderAltText"><xsl:variable name="i18nid"><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'id'"/></xsl:call-template></xsl:variable><xsl:if test="hl7:id"><xsl:value-of select="concat($i18nid, ' = ',hl7:id[1]/@root, ' ', hl7:id[1]/@extension)"/></xsl:if></xsl:variable><xsl:variable name="renderElement" select="self::hl7:nonXMLBody/hl7:text | self::hl7:observationMedia/hl7:value"/><xsl:choose><!-- Minimal mitigation for security risk based on malicious input --><xsl:when test="$renderElement/hl7:reference[starts-with(translate(normalize-space(@value),'JAVASCRIPT','javascript'),'javascript')]"><pre title="{$renderAltText}"><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'securityRiskURLLabel'"/></xsl:call-template><b><i><xsl:value-of select="$renderElement/hl7:reference/@value"/></i></b></pre></xsl:when><!-- if there is a reference, use that in an iframe --><xsl:when test="$renderElement/hl7:reference"><xsl:variable name="source" select="string($renderElement/hl7:reference/@value)"/><xsl:variable name="lcSource" select="translate($source, $uc, $lc)"/><xsl:variable name="scrubbedSource" select="translate($source, $simple-sanitizer-match, $simple-sanitizer-replace)"/><xsl:message><xsl:value-of select="$source"/>,<xsl:value-of select="$lcSource"/></xsl:message><xsl:choose><xsl:when test="contains($lcSource,'javascript')"><p><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'javascript-injection-warning'"/></xsl:call-template></p><xsl:message><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'javascript-injection-warning'"/></xsl:call-template></xsl:message></xsl:when><xsl:when test="not($source = $scrubbedSource)"><p><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'malicious-content-warning'"/></xsl:call-template></p><xsl:message><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'malicious-content-warning'"/></xsl:call-template></xsl:message></xsl:when><xsl:when test="$renderElement[starts-with(@mediaType, 'image/')]"><img alt="{$renderAltText}" title="{$renderAltText}" src="{$scrubbedSource}"><xsl:if test="string-length($usemap) > 0"><xsl:attribute name="usemap"><xsl:value-of select="$usemap"/></xsl:attribute></xsl:if></img></xsl:when><xsl:otherwise><xsl:comment>[if lte IE 9]><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'iframe-warning-ie9'"/></xsl:call-template><![endif]</xsl:comment><xsl:comment>[if gt IE 9]></xsl:comment><xsl:choose><xsl:when test="$renderElement/@mediaType = 'application/pdf' and $limit-pdf = 'yes'"><div style="font-style: italic;"><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'iframe-warning-sandboxed-pdf'"/></xsl:call-template></div></xsl:when><xsl:otherwise><iframe name="{$renderID}" id="{$renderID}" width="100%" height="600" title="{$renderAltText}"><xsl:if test="$renderElement/@mediaType != 'application/pdf' or $limit-pdf = 'yes'"><xsl:attribute name="sandbox"/></xsl:if><xsl:attribute name="src"><xsl:value-of select="$source"/></xsl:attribute></iframe></xsl:otherwise></xsl:choose><xsl:comment><![endif]</xsl:comment></xsl:otherwise></xsl:choose></xsl:when><!-- This is an image of some sort --><xsl:when test="$renderElement[starts-with(@mediaType,'image/')]"><img alt="{$renderAltText}" title="{$renderAltText}"><xsl:if test="string-length($usemap) > 0"><xsl:attribute name="usemap"><xsl:value-of select="$usemap"/></xsl:attribute></xsl:if><xsl:attribute name="src"><xsl:value-of select="concat('data:',$renderElement/@mediaType,';base64,',$renderElement/text())"/></xsl:attribute></img></xsl:when><!-- This is something base64. Internet Explorer 11 and below will not be able to render PDF this way, but
IE 10 and 11 stopped supporting HTML conditionals so unable to check. Microsoft Edge, Safari, Chrome, Firefox is fine.
So we're good on all major browsers except IE 10 and 11.
--><xsl:when test="$renderElement[@representation = 'B64']"><xsl:comment>[if lte IE 9]><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'iframe-warning-pdf-ie9'"/></xsl:call-template><![endif]</xsl:comment><xsl:comment>[if gt IE 9]></xsl:comment><xsl:call-template name="getLocalizedString"><xsl:with-param name="pre" select="' '"/><xsl:with-param name="key" select="'If the contents are not displayed here, it may be offered as a download.'"/></xsl:call-template><xsl:choose><xsl:when test="$renderElement/@mediaType = 'application/pdf' and $limit-pdf = 'yes'"><div style="font-style: italic;"><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'iframe-warning-sandboxed-pdf'"/></xsl:call-template></div></xsl:when><xsl:otherwise><iframe name="{$renderID}" id="{$renderID}" width="100%" height="600" title="{$renderAltText}"><xsl:if test="$renderElement/@mediaType != 'application/pdf' or $limit-pdf = 'yes'"><xsl:attribute name="sandbox"/></xsl:if><xsl:attribute name="src"><xsl:value-of select="concat('data:', $renderElement/@mediaType, ';base64,', $renderElement/text())"/></xsl:attribute></iframe></xsl:otherwise></xsl:choose><xsl:comment><![endif]</xsl:comment></xsl:when><!-- This is plain text --><xsl:when test="$renderElement[not(@mediaType) or @mediaType='text/plain']"><pre title="{$renderAltText}"><xsl:value-of select="$renderElement/text()"/></pre></xsl:when><xsl:otherwise><pre title="{$renderAltText}"><xsl:call-template name="getLocalizedString"><xsl:with-param name="key" select="'Cannot display the text'"/></xsl:call-template></pre></xsl:otherwise></xsl:choose></xsl:template>
Produces a section title with at least an anchor based on relative position in the
document (for the Table of Contents), and a second anchor if the section has the @ID
tag
<xsl:template name="section-title"><xsl:param name="level" select="3"/><!--<xsl:if test="@ID">
<a name="{@ID}"/>
</xsl:if>--><xsl:element name="{concat('h', $level)}"><xsl:attribute name="id"><xsl:choose><xsl:when test="@ID"><xsl:value-of select="@ID"/></xsl:when><xsl:otherwise><xsl:apply-templates select="." mode="getAnchorName"/></xsl:otherwise></xsl:choose></xsl:attribute><xsl:if test="hl7:code"><xsl:attribute name="title"><xsl:call-template name="show-code-set"><xsl:with-param name="in" select="hl7:code"/><xsl:with-param name="sep" select="', '"/><xsl:with-param name="textonly" select="'true'"/></xsl:call-template></xsl:attribute></xsl:if><xsl:choose><xsl:when test="count(hl7:component/hl7:structuredBody/hl7:component[hl7:section]) > 1"><!-- Add link to go back to top if the document has more than one section, otherwise superfluous --><a href="#_toc"><xsl:apply-templates select="." mode="getTitleName"/></a></xsl:when><xsl:otherwise><xsl:apply-templates select="." mode="getTitleName"/></xsl:otherwise></xsl:choose></xsl:element></xsl:template>
Handle footnoteRef. Produces a superscript [n] where n is the occurence number of
this ref in the
whole document. Also adds a title with the first 50 characters of th footnote on the
number so you
don't have to navigate to the footnote and just continue to read.
Namespace
No namespace
Match
hl7:footnoteRef
Mode
#default
Import precedence
0
Source
<xsl:template match="hl7:footnoteRef"><xsl:variable name="idref" select="@IDREF"/><xsl:variable name="footNoteNum"><xsl:for-each select="//hl7:footnote"><xsl:if test="@ID = $idref"><xsl:value-of select="position()"/></xsl:if></xsl:for-each></xsl:variable><xsl:variable name="footNoteText"><xsl:copy-of select="//hl7:footnote[@ID = $idref]//text()"/></xsl:variable><sup><xsl:text>[</xsl:text><a href="#{$idref}"><!-- Render footnoteref with the first 50 characters of the text --><xsl:attribute name="title"><xsl:value-of select="substring($footNoteText, 1, 50)"/><xsl:if test="string-length($footNoteText) > 50"><xsl:text>...</xsl:text></xsl:if></xsl:attribute><xsl:value-of select="$footNoteNum"/></a><xsl:text>]</xsl:text></sup></xsl:template>
Handle RenderMultiMedia. This currently only handles GIF's and JPEG's. It could, however,
be extended
by including other image MIME types in the predicate and/or by generating <object>
or <applet>
tag with the correct params depending on the media type @ID =$imageRef referencedObject
Namespace
No namespace
Match
hl7:renderMultiMedia
Mode
#default
Import precedence
0
Source
<xsl:template match="hl7:renderMultiMedia"><xsl:variable name="imageRefs" select="@referencedObject"/><!--<xsl:variable name="imageRefs">
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="@referencedObject"/>
<xsl:with-param name="delimiters" select="' '"/>
</xsl:call-template>
</xsl:variable>--><xsl:variable name="referencedObjects" select="ancestor::hl7:ClinicalDocument//hl7:regionOfInterest[@ID = $imageRefs] | ancestor::hl7:ClinicalDocument//hl7:observationMedia[@ID = $imageRefs]"/><div><xsl:apply-templates select="hl7:caption"/><xsl:for-each select="$referencedObjects"><xsl:choose><xsl:when test="self::hl7:regionOfInterest"><!-- What we actually would want is an svg with fallback to just the image that renders the ROI on top of image
The only example (in the CDA standard itself) that we've seen so far has unusable coordinates. That for now
is not very encouraging to put in the effort, so we just render the images for now
--><xsl:apply-templates select=".//hl7:observationMedia"><!--<xsl:with-param name="usemap" select="@ID"/>--></xsl:apply-templates><!--<xsl:variable name="coords">
<xsl:variable name="tcoords">
<xsl:for-each select="hl7:value/@value">
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="translate(normalize-space($tcoords),' ',',')"/>
</xsl:variable>--><!--<svg id="graph" width="100%" height="400px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink">
<!-\- pattern -\->
<defs>
<pattern id="image" x="0%" y="0%" height="100%" width="100%" viewBox="0 0 512 512">
<image x="0%" y="0%" width="512" height="512" xlink:href="https://cdn3.iconfinder.com/data/icons/people-professions/512/Baby-512.png"/>
</pattern>
</defs>
<circle id="sd" class="medium" cx="5%" cy="40%" r="5%" fill="url(#image)" stroke="lightblue" stroke-width="0.5%"/>
</svg>--><!--<map id="{@ID}" name="{@ID}">
<xsl:choose>
<!-\- A circle defined by two (column,row) pairs. The first point is the center of the circle and the second point is a point on the perimeter of the circle. -\->
<xsl:when test="hl7:code/@code = 'CIRCLE'">
<area shape="circle" coords="{$coords}" alt="Computer" href="computer.htm"/>
</xsl:when>
<!-\- An ellipse defined by four (column,row) pairs, the first two points specifying the endpoints of the major axis and the second two points specifying the endpoints of the minor axis. -\->
<xsl:when test="hl7:code/@code = 'ELLIPSE'">
<area shape="poly" coords="{$coords}" alt="Computer" href="computer.htm"/>
</xsl:when>
<!-\- A single point denoted by a single (column,row) pair, or multiple points each denoted by a (column,row) pair. -\->
<xsl:when test="hl7:code/@code = 'POINT'">
<area shape="poly" coords="{$coords}" alt="Computer" href="computer.htm"/>
</xsl:when>
<!-\- A series of connected line segments with ordered vertices denoted by (column,row) pairs; if the first and last vertices are the same, it is a closed polygon. -\->
<xsl:when test="hl7:code/@code = 'POLY'">
<area shape="poly" coords="{$coords}" alt="Computer" href="computer.htm"/>
</xsl:when>
</xsl:choose>
</map>--></xsl:when><!-- Here is where the direct MultiMedia image referencing goes --><xsl:when test="self::hl7:observationMedia"><xsl:apply-templates select="."/></xsl:when></xsl:choose></xsl:for-each></div></xsl:template>
Handle one line of birth/death/multiple birth data
Parameters
in
One element with the child elements birthTime, deceasedInd, deceasedTime, multipleBirthInd,
multipleBirthOrderNumber. Each of those is optional and may bein the V3 namespace
or in another namespace like sdtc
Show elements with datatype CD, CE, CV, CO separated with the value in 'sep'. Calls
show-code
Parameters
in
Set of 0 to * elements
sep
Separator between output of different elements. Default ', ' and special is 'br' which
generates an HTML br tag
textonly
XSLT 1.0 will output a warning when you create an element inside an attribute/text
node/processing instruction. To prevent that warning, we should just prevent creation
of elements in that context. Set to 'true' if that's the case. Default is 'false'.
XSLT 1.0 will output a warning when you create an element inside an attribute/text
node/processing instruction. To prevent that warning, we should just prevent creation
of elements in that context. Set to 'true' if that's the case. Default is 'false'.
Retrieves a language dependant string from our language file such as a label based on a key. Returns string based on textLang, textLangDefault, the first two characters of the textLangDefault, e.g. 'en' in 'en-US' and finally
if all else fails just the key text.
Parameters
pre
Some text or space to prefix our string with
key
The key to find our text with
post
Some text like a colon or space to postfix our text with
Default language for retrieval of language dependant strings such as labels, e.g.
'en-US'. This is the fallback language in case the string is not available in the
actual language. See also textLang.
Actual language for retrieval of language dependant strings such as labels, e.g. 'en-US'.
Unless supplied, this is taken from the ClinicalDocument/language/@code attribute,
or in case that is not present from textlangDefault.
Currently unused. Unsupported by Internet Explorer. Text encoding to render the output
in. Defaults to UTF-8 which is fine for most environments. Could change into more
localized encodings such as cp-1252 (Windows Latin 1), iso-8859-1 (Latin 1), or shift-jis
(Japanese Kanji table))
Boolean value for whether the result document may contain JavaScript. Some environments
forbid the use of JavaScript. Without JavaScript, certain more dynamic features may
not work.
Absolute or relative URI to an external Cascading Stylesheet (CSS) file that contains
style attributes for custom markup, e.g. in the @styleCode attribute in Section.text
Determines if the document title and top level summary of header information (patient/guardian/author/encounter/documentationOf,
inFulfillmentOf) should be rendered. Defaults to "true", any other value is interpreted
as "do not render". Some systems may have a context around the rendering of the document
that would make rendering the header superfluous. Note that the footer, which may
be switched off separately contains everything that the header does and more.
Security parameter. May contain a vertical bar separated list of URI prefixes, such
as "http://www.example.com|https://www.example.com". See parameter limit-external-images for more detail.
Security parameter. When set to 'yes' limits the URIs to images (if any) to locally
attached images and/or images that are on the external-image-whitelist. When set to anything other than 'yes' also allows for arbitrary external images
(e.g. through http:// or https://). Default value is 'yes' which is considered defensive
against potential security risks that could stem from resources loaded from arbitrary
source.
Security parameter. When set to 'yes' sandboxes the iframe for pdfs. Sandboxed iframe disallow plug-ins, including the plug-in needed to render pdf.
Effectively this setting thus prohibits pdf rendering. When set to anything other
than 'yes', pdf carrying iframes are not sandboxed and pdf rendering is possible.
Default value is 'yes' which is considered defensive against potential security risks
that could stem from resources loaded from arbitrary source.
Privacy parameter. Accepts a comma separated list of patient ID root values (normally
OID's). When a patient ID is encountered with a root value in this list, then the
rendering of the extension will be xxx-xxx-xxx regardless of what the actual value
is. This is useful to prevent public display of for example the US SSN. Default is
to render any ID as it occurs in the document. Note that this setting only affects
human rendering and that it does not affect automated processing of the underlying
document. If the same value also occurs in the skip-ids list, then that takes precedence.
Privacy parameter. Accepts a comma separated list of patient ID root values (normally
OID's). When a patient ID is encountered with a root value in this list, then the
rendering of this ID will be skipped. This is useful to prevent public display of
for example the US SSN. Default is to render any ID as it occurs in the document.
Note that this setting only affects human rendering and that it does not affect automated
processing of the underlying document.
Determines if sections will receive numbering according to ClinicalDocument order.
Value 'true' activates numbering. Top level sections are 1, 2, 3, 4, sub level sections
are 1.1, 1.2, 1.2.1, 1.2.2 etc.
<xd:doc>
<xd:desc>
<xd:p>Handle renderMultiMedia. Produces one or more iframes depending on the number of IDREFS in @referencedObject. Can have a caption on all of them.</xd:p>
</xd:desc>
</xd:doc>
<xsl:template match="hl7:renderMultiMedia">
<xsl:variable name="idrefs">
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="@referencedObject"/>
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="ancestor::hl7:ClinicalDocument//hl7:observationMedia[@ID = $idrefs]"/>
</xsl:template>