Implement a client-side healthcare application using Saxon-CE and HL7 CDA

Use XSLT 2.0 in the browser to enhance views of clinical documents


The Health Level 7 (HL7) Clinical Document Architecture (CDA) is based on the same HL7 version 3 Reference Information Model used to develop interactions for HL7 v3 messaging. Whereas HL7 messaging represents individual healthcare interactions (such as queries, responses, and notifications), an HL7 CDA document is a single, persistent document that represents a patient visit, a continuity-of-care document, and the like. To make the entries in a CDA readable by people, each has a text description in addition to any machine-readable content, as in Listing 1. HL7 v3 messages are, strictly speaking, intended for transmission over a wire (for example, as part of a client-server interaction or server-server data distribution); because they are also intended for human readability, HL7 CDA documents can be transferred by other means, such as email.

Listing 1. An observation entry in CDA: Penicillin allergy
        <code code="10155-0" codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC"/>
        <title>Allergies and Adverse Reactions</title>
                <item>Penicillin - Hives</item>
            <observation classCode="OBS" moodCode="EVN">
                <code xsi:type="CD" code="247472004" codeSystem="2.16.840.1.113883.6.96" 
                    codeSystemName="SNOMED CT" displayName="Hives"/>
                <statusCode code="completed"/>
                <entryRelationship typeCode="MFST">
                    <observation classCode="OBS" moodCode="EVN">
                        <code xsi:type="CD" code="91936005" 
                            codeSystemName="SNOMED CT" 
                            displayName="Allergy to penicillin"/>
                        <statusCode code="completed"/>

The Saxon parser has supported XSLT 2.0 transformations on the server side for several years now. Recently, at the 2011 XML Prague conference, Dr. Michael Kay, who wrote the original Saxon parser, introduced an alpha release of Saxon Client Edition (Saxon-CE), which allows XSLT 2.0 transformations to be run directly from the web client application (in the browser). Saxon-CE uses JavaScript to run on any modern browser, which has been an ongoing issue with XSL on the client side: Historically, XSL support has not been consistently implemented by the different browsers. Because it uses JavaScript as a run-time engine, Saxon-CE resolves this issue.

In addition, XSLT 2.0 provides several features that were not supported in the original XSLT 1.0 specification, such as support for multiple documents and stronger data typing. With Saxon-CE, these features are now supported in the client, and, as this article demonstrates, some of these features are ideally suited for use in client application views—in particular, client-side support for multiple documents.

Getting started

To develop the CDA Viewer application described in this article, I used the Unite web server that comes packaged with the latest Opera release. You could use any application server you prefer. Because I am describing how to develop a client application view, I don't describe how the CDA document is persisted or distributed. To test my application view, I simply publish a sample CDA as a URL directly through the web server (alternatively, you might persist these in an XML database such as IBM® DB2® pureXML). Similarly, my XSL transforms are published as URLs.

Downloading Saxon-CE

The Saxon-CE alpha release is currently available for download from the Saxonica website. This alpha release is not intended for production applications at this point; however, based on the success of the original Saxon parser, I have no doubt that the Client Edition will also quickly become an industry standard, especially if it meets with positive feedback from early adopters.

The alpha release comes with comprehensive release notes. You can copy the JavaScript files contained in the unzipped release folder to your application server, as you would any other .js scripts. You should also take some time to download the sample applications. The JavaScript consists of a single .nocache.js file along with a number of minified .js files—one for each major browser. These files contain the actual parser code; the .nocache.js file detects which browser is in use and then calls out to the appropriate .js script. The sample applications consist of an XSL transform file, some XML data, and an HTML file that calls the Saxon-CE JavaScript to run the transform. The CDA Viewer application that I built follows the same pattern, although I broke my transformation layer into three files for readability (see Download). On my web server, I have one folder for each of the sample applications and then one for my CDA application.

Exploring the features in XSLT 2.0

Many features were introduced in the XSLT 2.0 specification—features like function definitions, multiple result documents, sequences and temporary trees, stronger typing, and text parsing. This article highlights support for multiple result documents. On the server side, using multiple result documents means creating multiple documents on the server, so that you can create an XHTML home page that references several other result pages, all generated on the server. In the context of server-side transformation, using multiple result documents means just that: multiple documents. Listing 2 demonstrates this approach. When you generate multiple documents on the client side using Saxon-CE, the generated content is actually targeted to different screen areas of your client application (typically, div elements in the HTML page).

Listing 2. Multiple result documents (on the server)
<xsl:for-each select="section">
    <xsl:result-document href="{@id}.html">
        <xsl:apply-templates select="." mode="html" />

Transforming the clinical document

I organized my XSL transformations into three files (see Download):

  • cda.xsl. Contains the basic transformation to display the human-readable document
  • cda-machine-readable.xsl. Contains a customizable layer that currently displays the machine-readable details of the document in a tidier fashion
  • cda-dom-interaction.xsl. Contains templates that handle interactions with the DOM of the HTML page, such as click events

In addition, there are a CSS file and the HTML page itself.

The CSS is straightforward. The HTML page is likewise straightforward, containing a number of div elements for the different screen areas of the CDA Viewer, each of which can be positioned by an ID in the CSS. My plan is to load the entire CDA and transform it, then allow interactive access through the DOM so that I have a div element called div-cache, which I have hidden at the bottom of the viewer. This cache contains a copy of each of the entries recorded in the CDA. You can scroll down and see this text, or you can make this element hidden by marking it style="visibility:hidden;inline:none".

The code in Listing 3 triggers the transformation process. As you can see, <script> tags call out to first the Saxon-CE JavaScript and then the XSL transformation stylesheet, targeting the CDA document.

Listing 3. Invoking Saxon-CE from the HTML page
<script type="text/javascript" language="javascript" src="../Saxonce.nocache.js"></script>
<script type="application/xslt+xml" language="xslt2.0" src="cda.xsl" 

Displaying the CDA Viewer's table of contents

In Listing 3, the main transformation first creates a simplified header for the CDA Viewer by extracting some of the information from the CDA wrapper. Already, you can see xsl:result-document in action. In Listing 4, notice how the <div> tag is targeted by its #creation ID. method="ixsl:replace-content" is Saxon-CE shorthand for "in the interactive XSL, replace the content." You'll see an example in a moment of appending content. Notice that when you deal directly with the CDA, you need to use the hl7: namespace whenever you refer to an element in the CDA. You will see later that this isn't necessary when you deal with templates that refer to the DOM. This process can be tricky and is one reason why I separated my templates into several XSL style sheets.

Listing 4. Replacing content with xsl:result-document
<xsl:result-document href="#creation" method="ixsl:replace-content">
            <xsl:value-of select="hl7:ClinicalDocument/hl7:title"/>: 

In the next piece of code from the same XSL file (see Listing 5), you can see how the table of contents for the CDA Viewer is generated from the sections in the CDA document. Take a look at the sample files provided for details on how the table of contents is formatted. A similar approach is used to cache the clinical entry data from the CDA. In both cases, you can see how the results are appended to the existing contents of the targeted <div> element each time the template is applied.

Listing 5. Appending content with xsl:result-document
<xsl:result-document href="#notes" method="ixsl:append-content">
    <xsl:apply-templates select="//hl7:component/hl7:section" mode="notes"/>

<xsl:result-document href="#div-cache" method="ixsl:append-content">
    <xsl:apply-templates select="//hl7:component/hl7:section" mode="cache"/>

Figure 1 shows the basic CDA Viewer and table of contents.

Figure 1. The CDA Viewer table of contents
Screen capture of the table of contents for the CDA View application
Screen capture of the table of contents for the CDA View application

Interacting with the DOM

Generated IDs are used to connect links in the table of contents to the cached CDA entries. Listing 6 provides an excerpt from the template used to create an entry in the table of contents.

Listing 6. Linking to the cached CDA entry
<a class="notes-section">
    <xsl:attribute name="id"><xsl:value-of select="generate-id(.)"/></xsl:attribute>
    <xsl:value-of select="hl7:title"/>

In Listing 7, notice how the mode="ixsl:onclick" attribute on the template is used to tell Saxon-CE to apply this template to handle a click event. This is no different than any other JavaScript click event: It is simply handled by Saxon-CE and the XSL stylesheet. Note that whenever the parser handles a click event like this, the context shifts from the original XML document to the DOM of the HTML page, which is why I separated these templates into a transformation stylesheet called cda-dom-interaction.xsl. The template itself displays the text of the CDA entry, extracted from the cache created earlier using the ID associated with the original click event (the ID of the original anchor link). This text replaces the content of the note-detail result document div element. The template then applies any applicable downstream templates to deal with machine-readable content in the CDA entry.

Listing 7. Handling a click event from the table of contents
<xsl:template match="a[@class='notes-section']" mode="ixsl:onclick">
    <xsl:variable name="div-id" select="@id"/>
    <xsl:variable name="selected-section" 

    <xsl:result-document href="#note-detail" method="ixsl:replace-content">
        <h2><xsl:value-of select="$selected-section/title/text()"/> 
            (<xsl:value-of select="$div-id"/>)</h2>
        <div><xsl:value-of select="$selected-section/text"/></div>
        <h2>Machine-readable Content</h2>
        <xsl:apply-templates select="$selected-section" mode="mach-read">
            <xsl:with-param name="a" select="."/>

To keep the machine-readable and human-readable concerns separate, I created and included a third transformation stylesheet called cda-machine-readable.xsl. Keeping these templates separate has the advantage that this third stylesheet can be extended or completely replaced to use a more sophisticated stylesheet to handling the machine-readable information in the CDA. You might want to do this, for instance, to provide fancier tables or graphic representations of data or to withhold certain types of entries. Alternatively, you might want to go deep and load another XML document, perhaps from a metadata repository, and expand on some of the codes within the machine-readable data or categorize them using common code systems. Keeping the templates for transforming machine-readable entries is good programming practice, as is separation of concerns in general. I have found this practice essential when dealing with large-scale transformations or transformations I expect to grow.

Displaying machine-readable clinical entries

My minimalist machine-readable transformation stylesheet contains a single template that identifies a number of known entry types (Observation, Substance Administration, Procedure, and Act events) and styles them using bulleted lists and tables. I made no attempt to do anything too fancy here, but it is a useful exercise to look at how a simple template can provide a substantial return in style and organization. Also notice how in Listing 8 the originating anchor link is passed to the template using a parameter. This step is not necessary to display the machine-readable data; rather, the link is passed through this template and cached for later use in a hidden div element at the bottom of the entry detail. This link information will subsequently be used to build what I am calling a pin area. For the sake of brevity, I omitted all but the xsl:when for Observation in the xsl:choose.

Listing 8. Templating machine-readable CDA entries
<xsl:template match="section" mode="mach-read">
    <xsl:param name="a"/>

    <div id="mach-read">
            <xsl:when test="entry/observation">
                    <xsl:for-each select="entry/observation">
                            <xsl:value-of select="text"/>
                            <xsl:value-of select="code/@displayName"/>
                            <xsl:for-each select="value">
                            (<xsl:value-of><xsl:value-of select="@value"/> 
                                <xsl:value-of select="@unit"/></xsl:value-of>)
            <xsl:otherwise>(no match)</xsl:otherwise>
        <div class="xref" style="visibility:hidden;inline:none">
            <xsl:copy-of select="$a"/>

Notice that because this template is applied to a copy of the original CDA data that was cached in the DOM, the hl7: is not required for elements like entry, code, and observation. If you want, you can add this namespace to the cached CDA entries, but there is little benefit for doing so because no further serialization is performed. If there were any ambiguity between HL7 CDA elements and other elements on the page, doing so would be pragmatic.

Figure 2 shows the CDA Viewer displaying an observation event representing allergies and adverse reactions.

Figure 2. Observation events, such as allergies and adverse reactions
Screen capture of the CDA Viewer showing observation events,such as allergies and adverse reactions
Screen capture of the CDA Viewer showing observation events,such as allergies and adverse reactions

Developing a new design pattern: The pin area

The pin area is a design pattern that emerged during development of these transformations, inspired by the way Saxon-CE implements XSLT 2.0 result documents. As previously described, generated content either can be appended to a screen area or can replace existing content. Typically, you will want to replace existing content; however, when you are applying a template repeatedly, you want to append content. If you apply several templates to the same target screen area, you can create a collection that resembles a bookmark: You can interactively add to this collection over time without removing any of the existing content in the screen area.

In the sample code in Listing 9, look at the template to pin anchor links. Granted, this is a sample application, but the idea here is intriguing. When you click a link in the Viewer's table of contents, the click event is handled by displaying the contents of that section of the CDA. When you click any other link, however, a simple anchor template handles the link event and copies the contents of the link to the pin area. The result is that you can use the Viewer to sift through the contents of the clinical document and "pin" entries for later reference.

Listing 9. Templating the pin area
<xsl:template match="a" mode="ixsl:onclick">
    <xsl:variable name="div-mach-read" select="ancestor::div[@id='mach-read']"/>
    <xsl:result-document href="#pin" method="ixsl:append-content">
            <xsl:attribute name="title"><xsl:value-of select="$div-mach-read/h3"/> 
            - <xsl:value-of select="text()"/></xsl:attribute>
            <xsl:copy-of select="$div-mach-read/div[@class='xref']/a"/> -
            <xsl:value-of select="text()"/>

Figure 3 shows the pin area in use.

Figure 3. The pin area of the CDA Viewer
Screen capture of the pin area of the CDA Viewer with items noted in it
Screen capture of the pin area of the CDA Viewer with items noted in it

The benefits of this design pattern are, of course, increased if you can do something useful with the pinned entries; however, that discussion goes beyond the scope of this article. A potential use case might involve a plug-in to an email browser that allowed a patient to view a CDA received from his or her practitioner, pin some entries, and then enclose them in an email response. Because of the way they handle information declaratively, Saxon-CE and XSLT 2.0 will no doubt prove to be useful tools in developing these applications.


CDA documents are persistent and as a result are often quite large. Although they are intended to be human-readable, CDA documents also contain a wealth of machine-readable information, which can be made more manageable by transformation into an expanded client view. I'm sure that with future releases, Saxon-CE will prove to be an essential tool for doing just this because it can run on any modern browser and because it offers client-side XSLT 2.0 features such as multiple result document support. I fully expect this technology to become indispensable to developers of rich healthcare applications.

Downloadable resources

Related topics


Sign in or register to add and subscribe to comments.

Zone=XML, Industries
ArticleTitle=Implement a client-side healthcare application using Saxon-CE and HL7 CDA