Thursday, February 28, 2013

Comparing CDA and HL7 V3 id elements in XSLT

One of the more frustrating things I've had to write repeatedly is the XSLT expression that allows me to compare two identifiers in HL7 V3 artifacts (including CDA).  The use case that I was working on was to determine whether the legal authenticator of the document was also an author.  I'm building something which displays the names and contact information for pertinent people described in the CDA header.  I wanted to be sure that I didn't re-display the legal authenticator if that person had already been listed as an author.

Assuming that I have a rule that the identifier of each person is always included, one way to do that would be to compare two identifiers to see if they are equal.  What I want to say is: "Select the legal authenticator if there does not exist an author who has the same id".


In XSLT, this would look something like:
<xsl:template 
  match="cda:legalAuthenticator[if there does not exist an author with the same id]">
  <!-- handle display of the legal authenticator -->
</xsl:template>


The first step in developing the predicate (the expression inside the brackets []) is comparing two identifiers.  If $IDx and $IDy are variables representing two identifiers, I can compare them for equality as follows:
 $IDx/@root = $IDy/@root and 
 ($IDx/@extension = $IDy/@extension or 
  (not($IDx/@extension) and not($IDy/@extension))
 )

That's complicated enough to start with, but it deals correctly with the fact that the extension attribute is optional in an element using the II data type.  The following is much simpler and also works:

 $IDx/@root = $IDy/@root and (string($IDx/@extension) = string($IDy/@extension))


It takes advantage of the fact that string() returns the empty string if the node-set passed to it is empty.

But, both the author and the legalAuthenticator element can have multiple identifiers, which makes this even more challenging.  What I really need to say in my predicate is: Select all legalAuthenticator elements such that for all of their id elements, there does not exist an author with a matching identifier in any of its id elements.  I need a way to find authors with a matching identifier.

Something like this looks like it might work:

    cda:legalAuthenticator[
      (cda:id/@root = ../cda:author/cda:assignedAuthor/cda:id/@root) and
      (string(cda:id/@extension) = 
       string(../cda:author/cda:assignedAuthor/cda:id/@extension)
      )
    ]


This fails because of the way XSLT evaluates the comparison between the node-sets using the equal operator.  So, I cannot compare legalAuthenticator/assignedEntity/id/@root and to author/assignedAuthor/id/@root and legalAuthenticator/assignedEntity/id/@extension author/assignedAuthor/id/@extension in separate expressions like that, because I could have a matching root in one node, and a matching extension in node, and the result would evaluate to true, which would be wrong. This is a subtle error which only shows up in a very limited list of data sets.  The following presents an example of XML that would cause this evaluation to fail.

<author>
  <assignedAuthor>
    <id root="1" extension="1"/>
    <id root="0" extension="0"/>
  </assignedAuthor>
</author>
<legalAuthenticator>
  <assignedEntity>
    <id root="1" exension="0"/>
  </assignedEntity>
</legalAuthenticator>

So, somehow, I have to be able to compare @root and string(@extension) together as a unit.  It would certainly be nice if there was a function I could use to look up an element by a function over a set of attributes.  That is in fact, the key [pun intended] to my solution.

We define a key for author identifiers based on the id/@root and id/@extension values as follows:

  <xsl:key name="authorIDs" 
    match="/cda:ClinicalDocument/cda:author/cda:assignedAuthor/cda:id"
    use="concat(@root,':',string(@extension))"/>

Later, when I want to test the legalAuthenticator element, I write the predicate thus:



    cda:legalAuthenticator[
     not(
      cda:assignedEntity/cda:id[
       count(
        key('authorIDs',concat(@root,':',string(@extension)) )
       )!=0
      ]
     )
    ]



This says: Select legalAuthenticator if there does not exist an assignedEntity/id in the legalAuthenticator such that the key constructed from its @root and @extension attributes that matches any of the keys used for an author identifier.

Done.  And a lot more elegantly than I would have thought, although I gotta say, XPath is a nasty language to think in.  Oh well, power has its own price.

  -- Keith

P.S.  If you want to simplify further, you can eliminate the count(...)!=0.  I leave that in because it helps to document what the code is trying to do.




0 comments:

Post a Comment