Created
July 13, 2015 16:19
-
-
Save kwboone/95759c7d96f0028213d0 to your computer and use it in GitHub Desktop.
Compares HL7 CDA templates developed using Trifolia
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<!-- | |
This stylesheet compares Implementation Guides in Trifolia format (see trifolia.lantanagroup.com) | |
It takes one parameter: newVersion, which identifies the filename of the new version of the guide to compare. | |
Its input is the older version of the guide. | |
Its output is HTML. It produces a section in the HTML for each template in the old guide that is present in some version in the new guide. The table heading | |
compares the template metadata and highlights changed rows in the first column. It then compares the Narrative text constraints for the two templates, using | |
heuristics to find the closest matching template constraints based on context and RIM class used for the template. | |
Differences are highlighted in a color based on the strength of the original conformance constraint: | |
RED = SHALL | |
YELLOW = SHOULD | |
GREEN = MAY | |
WHITE = No change | |
Text in the narrative constraint contained inside () is ignored during the comparison. | |
Copyright (c) 2015 by Keith W. Boone | |
--> | |
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" | |
xmlns:lg="http://www.lantanagroup.com" xmlns:set="http://exslt.org/sets" | |
xmlns:exslt="http://exslt.org/common" extension-element-prefixes="set exslt" version="1.0"> | |
<xsl:param name="newVersion" select="'C-CDA R2.1.xml'"/> | |
<xsl:variable name="currentVersionDoc" select="document($newVersion)"/> | |
<xsl:variable name="currentVersion"> | |
<xsl:apply-templates select="$currentVersionDoc" mode="copy"/> | |
</xsl:variable> | |
<xsl:variable name="current" select="exslt:node-set($currentVersion)"/> | |
<xsl:variable name="refactored"> | |
<xsl:apply-templates select="/" mode="copy"/> | |
</xsl:variable> | |
<xsl:template match="/"> | |
<xsl:variable name="processed"> | |
<xsl:call-template name="process"/> | |
</xsl:variable> | |
<xsl:apply-templates select="exslt:node-set($processed)" mode="fixTable"/> | |
</xsl:template> | |
<xsl:template name="process"> | |
<html> | |
<style> | |
.MAY { | |
background-color: C0FFC0; | |
} | |
.SHOULD { | |
background-color: FFFFC0; | |
} | |
.SHALL { | |
background-color: FFC0C0; | |
} | |
.different { | |
background-color: C0C0C0; | |
}</style> | |
<body> | |
<xsl:for-each select="exslt:node-set($refactored)//lg:Template"> | |
<xsl:variable name="id" | |
select="substring-after(substring-after(@identifier,':'),':')"/> | |
<xsl:call-template name="compare"> | |
<xsl:with-param name="old" select="."/> | |
<!--select="document(concat($original, '\', $id, '.xml'))//lg:Template"/>--> | |
<xsl:with-param name="new" | |
select="$current//lg:Template[contains(@identifier, concat(":",$id,":"))]"/> | |
<!--select="document(concat($current, '\', $id, '.xml'))//lg:Template"/>--> | |
</xsl:call-template> | |
</xsl:for-each> | |
</body> | |
</html> | |
</xsl:template> | |
<xsl:template name="compare"> | |
<xsl:param name="old"/> | |
<xsl:param name="new"/> | |
<xsl:if test="count($new//*) != 0"> | |
<section> | |
<h2> | |
<xsl:value-of select="$old/@title"/> | |
</h2> | |
<table border="1" cellspacing="0"> | |
<col width="40%"/> | |
<col width="40%"/> | |
<thead> | |
<tr> | |
<th width="20%">Feature</th> | |
<th width="40%">Original</th> | |
<th width="40%">New</th> | |
</tr> | |
</thead> | |
<thead> | |
<tr> | |
<th align="left" width="20%"> | |
<xsl:if | |
test="$old/@owningImplementationGuideName != $new/@owningImplementationGuideName"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>IG</xsl:text> | |
</th> | |
<td width="40%"> | |
<xsl:value-of select="$old/@owningImplementationGuideName"/> | |
</td> | |
<td width="40%"> | |
<xsl:value-of select="$new/@owningImplementationGuideName"/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:if test="$old/@title != $new/@title"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>title</xsl:text> | |
</th> | |
<td> | |
<xsl:value-of select="$old/@title"/> | |
</td> | |
<td> | |
<xsl:value-of select="$new/@title"/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:if | |
test="substring-before(substring-after(substring-after(concat($old/@identifier,':'),':'),':'),':') != | |
substring-before(substring-after(substring-after(concat($new/@identifier,':'),':'),':'),':')"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>identifier</xsl:text> | |
</th> | |
<td> | |
<xsl:value-of | |
select="substring-before(substring-after(substring-after(concat($old/@identifier,':'),':'),':'),':')" | |
/> | |
</td> | |
<td> | |
<xsl:value-of | |
select="substring-before(substring-after(substring-after(concat($new/@identifier,':'),':'),':'),':')" | |
/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:if | |
test="substring-after(substring-after(substring-after(concat($old/@identifier,':'),':'),':'),':') != | |
substring-after(substring-after(substring-after(concat($new/@identifier,':'),':'),':'),':')"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>version</xsl:text> | |
</th> | |
<td> | |
<xsl:value-of | |
select="substring-after(substring-after(substring-after(concat($old/@identifier,':'),':'),':'),':')" | |
/> | |
</td> | |
<td> | |
<xsl:value-of | |
select="substring-after(substring-after(substring-after(concat($new/@identifier,':'),':'),':'),':')" | |
/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:if test="$old/@publishStatus != $new/@publishStatus"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>publishStatus</xsl:text> | |
</th> | |
<td> | |
<xsl:value-of select="$old/@publishStatus"/> | |
</td> | |
<td> | |
<xsl:value-of select="$new/@publishStatus"/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:if test="$old/@templateType != $new/@templateType"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>templateType</xsl:text> | |
</th> | |
<td> | |
<xsl:value-of select="$old/@templateType"/> | |
</td> | |
<td> | |
<xsl:value-of select="$new/@templateType"/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:if test="$old/@isOpen != $new/@isOpen"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>isOpen</xsl:text> | |
</th> | |
<td> | |
<xsl:value-of select="$old/@isOpen"/> | |
</td> | |
<td> | |
<xsl:value-of select="$new/@isOpen"/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:if | |
test="string($old/lg:PreviousVersion) != string($new/lg:PreviousVersion)"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>PreviousVersion</xsl:text> | |
</th> | |
<td> | |
<xsl:apply-templates select="$old/lg:PreviousVersion"/> | |
</td> | |
<td> | |
<xsl:apply-templates select="$new/lg:PreviousVersion"/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:if | |
test="string($old/lg:Description) != string($new/lg:Description)"> | |
<xsl:attribute name="class">different</xsl:attribute> | |
</xsl:if> | |
<xsl:text>Description</xsl:text> | |
</th> | |
<td> | |
<xsl:apply-templates select="$old/lg:Description"/> | |
</td> | |
<td> | |
<xsl:apply-templates select="$new/lg:Description"/> | |
</td> | |
</tr> | |
<tr> | |
<th align="left"> | |
<xsl:value-of select="$old/@contextType"/> | |
</th> | |
<td colspan='2'> </td> | |
</tr> | |
</thead> | |
<xsl:call-template name="contrastContexts"> | |
<xsl:with-param name="contexts" | |
select="set:distinct($old/lg:Constraint/@context|$new/lg:Constraint/@context)"/> | |
<xsl:with-param name="oldP" select="$old"/> | |
<xsl:with-param name="newP" select="$new"/> | |
<xsl:with-param name="previous" select="concat($old/@context,'/')"/> | |
</xsl:call-template> | |
</table> | |
</section> | |
</xsl:if> | |
</xsl:template> | |
<xsl:template match="lg:PreviousVersion"> | |
<p><xsl:value-of select="@name"/> (<xsl:value-of select="@identifier"/>)</p> | |
</xsl:template> | |
<xsl:template match="lg:Description|lg:NarrativeText"> | |
<p> | |
<xsl:value-of select="text()"/> | |
</p> | |
</xsl:template> | |
<xsl:template name="contrastContexts"> | |
<xsl:param name="contexts"/> | |
<xsl:param name="oldP"/> | |
<xsl:param name="newP"/> | |
<xsl:param name="previous"/> | |
<tbody> | |
<!-- Nexting tbody's this way isn't really legal, but I'm going to fix it in a post processing pass | |
Counting the max depth of tbody will tell me how many columns to add at the beginning of the table | |
and how to add rowspans to the first td of each tr. | |
--> | |
<xsl:for-each select="$contexts"> | |
<xsl:sort select="." data-type="text"/> | |
<xsl:variable name="ctx" select="string(.)"/> | |
<xsl:variable name="oldCL" select="$oldP/lg:Constraint[@context = $ctx]"/> | |
<xsl:variable name="newCL" select="$newP/lg:Constraint[@context = $ctx]"/> | |
<xsl:variable name="numbers" select="set:distinct($oldCL/@number|$newCL/@number)"/> | |
<!-- Only split them by number if count($oldCL) != 1 or count ($newCL != 1) --> | |
<xsl:choose> | |
<xsl:when test="count($oldCL) > 1 or count($newCL) > 1"> | |
<xsl:for-each select="$numbers"> | |
<xsl:sort select="." data-type="number"/> | |
<xsl:variable name="number" select="string(.)"/> | |
<xsl:variable name="oldC" | |
select="$oldP/lg:Constraint[@context = $ctx and @number = $number]"/> | |
<xsl:variable name="newC" | |
select="$newP/lg:Constraint[@context = $ctx and @number = $number]"/> | |
<xsl:call-template name="compareConstraint"> | |
<xsl:with-param name="ctx" select="$ctx"/> | |
<xsl:with-param name="oldC" select="$oldC"/> | |
<xsl:with-param name="newC" select="$newC"/> | |
<xsl:with-param name="previous" select="$previous"/> | |
<xsl:with-param name="numbers" select="true()"/> | |
</xsl:call-template> | |
</xsl:for-each> | |
</xsl:when> | |
<xsl:otherwise> | |
<xsl:variable name="number" select="string(.)"/> | |
<xsl:variable name="oldC" select="$oldCL"/> | |
<xsl:variable name="newC" select="$newCL"/> | |
<xsl:call-template name="compareConstraint"> | |
<xsl:with-param name="ctx" select="$ctx"/> | |
<xsl:with-param name="oldC" select="$oldCL"/> | |
<xsl:with-param name="newC" select="$newCL"/> | |
<xsl:with-param name="previous" select="$previous"/> | |
<xsl:with-param name="numbers" select="false()"/> | |
</xsl:call-template> | |
</xsl:otherwise> | |
</xsl:choose> | |
</xsl:for-each> | |
</tbody> | |
</xsl:template> | |
<xsl:template name="compareConstraint"> | |
<xsl:param name="oldC"/> | |
<xsl:param name="newC"/> | |
<xsl:param name="ctx"/> | |
<xsl:param name="previous"/> | |
<xsl:param name="numbers"/> | |
<xsl:variable name="oldNarr"> | |
<xsl:apply-templates select="$oldC/lg:NarrativeText/text()" mode="narrative"/> | |
</xsl:variable> | |
<xsl:variable name="newNarr"> | |
<xsl:apply-templates select="$newC/lg:NarrativeText/text()" mode="narrative"/> | |
</xsl:variable> | |
<tr> | |
<th align="left"> | |
<xsl:if | |
test=" translate(normalize-space(string($oldNarr)),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') != | |
translate(normalize-space(string($newNarr)),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') "> | |
<xsl:attribute name="class"> | |
<xsl:value-of select="$oldC/@conformance"/> | |
</xsl:attribute> | |
</xsl:if> | |
<p> | |
<!-- <xsl:value-of select="$previous"/> --> | |
<xsl:value-of select="$ctx"/> | |
<xsl:if test="$numbers"> | |
<xsl:text> (</xsl:text> | |
<xsl:value-of select="$oldC/@number"/> | |
<xsl:text>/</xsl:text> | |
<xsl:value-of select="$newC/@number"/> | |
<xsl:text>)</xsl:text> | |
</xsl:if> | |
</p> | |
</th> | |
<td> | |
<p> | |
<xsl:value-of select="$oldC/lg:NarrativeText/text()"/> | |
</p> | |
</td> | |
<td> | |
<p> | |
<xsl:value-of select="$newC/lg:NarrativeText/text()"/> | |
</p> | |
</td> | |
</tr> | |
<xsl:if test="$oldC/lg:Constraint|$newC/lg:Constraint"> | |
<xsl:call-template name="contrastContexts"> | |
<xsl:with-param name="contexts" | |
select="set:distinct($oldC/lg:Constraint/@context|$newC/lg:Constraint/@context)"/> | |
<xsl:with-param name="oldP" select="$oldC"/> | |
<xsl:with-param name="newP" select="$newC"/> | |
<xsl:with-param name="previous" | |
select="concat($previous,substring-before(concat($ctx,'['),'['))" | |
/> | |
</xsl:call-template> | |
</xsl:if> | |
</xsl:template> | |
<!-- Strip non-essential text from text constraints for comparison of differences: | |
The numbers in the CONF: preceding the - | |
The variance in versioned identifiers starting with urn:oid and urn:hl7ii | |
--> | |
<xsl:template name="stripParenthetical" match="text()" mode="narrative"> | |
<xsl:param name="text" select="."/> | |
<xsl:variable name="left" select="normalize-space(substring-before($text,'('))"/> | |
<xsl:variable name="right" select="normalize-space(substring-after($text,')'))"/> | |
<xsl:value-of select="$left"/> | |
<xsl:if test="$right!=''"> | |
<xsl:call-template name="stripParenthetical"> | |
<xsl:with-param name="text" select="$right"/> | |
</xsl:call-template> | |
</xsl:if> | |
</xsl:template> | |
<!-- | |
When you see this: | |
<Constraint isBranch="true" isStatic="true" cardinality="1..*" number="8939" context="entryRelationship" isVerbose="false" conformance="SHALL"> | |
<NarrativeText>SHALL contain at least one [1..*] entryRelationship (CONF:8939) such that it</NarrativeText> | |
<Constraint isBranchIdentifier="true" isStatic="true" cardinality="1..1" number="8940" context="@typeCode" isVerbose="false" conformance="SHALL"> | |
<SingleValueCode code="REFR" displayName="Refers to" /> | |
<CodeSystem oid="urn:oid:2.16.840.1.113883.5.1002" /> | |
<NarrativeText>SHALL contain exactly one [1..1] @typeCode="REFR" Refers to (CodeSystem: HL7ActRelationshipType urn:oid:2.16.840.1.113883.5.1002 STATIC) (CONF:8940).</NarrativeText> | |
</Constraint> | |
Change it to: | |
<Constraint isBranch="true" isStatic="true" cardinality="1..*" number="8939" context="entryRelationship[@typeCode='REFR']" isVerbose="false" conformance="SHALL"> | |
<NarrativeText>SHALL contain at least one [1..*] entryRelationship (CONF:8939) such that it<br/> | |
SHALL contain exactly one [1..1] @typeCode="REFR" Refers to (CodeSystem: HL7ActRelationshipType urn:oid:2.16.840.1.113883.5.1002 STATIC) (CONF:8940).<br/> | |
AND | |
</NarrativeText> | |
--> | |
<xsl:template match="lg:Constraint[lg:Constraint/@context='@typeCode']" mode="copy"> | |
<xsl:copy> | |
<xsl:copy-of select="@*[not(local-name()='context')]"/> | |
<xsl:attribute name="context"> | |
<xsl:value-of select="@context"/> | |
<xsl:text>[@typeCode='</xsl:text> | |
<xsl:value-of select="lg:Constraint[@context='@typeCode']/lg:SingleValueCode/@code"/> | |
<xsl:text>' and </xsl:text> | |
<xsl:value-of select="lg:Constraint/@containedTemplateType"/> | |
<xsl:text>]</xsl:text> | |
</xsl:attribute> | |
<xsl:apply-templates select="*[not(@context='@typeCode')]" mode="copy"/> | |
</xsl:copy> | |
</xsl:template> | |
<xsl:template match="lg:NarrativeText[../lg:Constraint/@context='@typeCode']" mode="copy"> | |
<xsl:copy> | |
<xsl:copy-of select="@*"/> | |
<xsl:value-of select="."/> | |
<xsl:text>
</xsl:text> | |
<xsl:value-of select="../lg:Constraint[@context='@typeCode']/lg:NarrativeText"/> | |
<xsl:text>
 AND
</xsl:text> | |
</xsl:copy> | |
</xsl:template> | |
<xsl:template match="@*|node()" mode="copy"> | |
<xsl:copy> | |
<xsl:apply-templates select="@*|node()" mode="copy"/> | |
</xsl:copy> | |
</xsl:template> | |
<xsl:template name="computeDepth"> | |
<xsl:for-each select="//tbody"> | |
<xsl:sort select="count(ancestor-or-self::tbody)" order="descending" data-type="number"/> | |
<xsl:if test="position() = 1"> | |
<xsl:value-of select="count(ancestor-or-self::tbody)"/> | |
</xsl:if> | |
</xsl:for-each> | |
</xsl:template> | |
<xsl:template name="cols"> | |
<xsl:param name='count'/> | |
<xsl:if test='$count != 0'> | |
<col width='1%'/> | |
<xsl:call-template name="cols"> | |
<xsl:with-param name="count" select="$count - 1"/> | |
</xsl:call-template> | |
</xsl:if> | |
</xsl:template> | |
<!-- compute the maximum depth in tbody sections --> | |
<xsl:template match="html" mode="fixTable"> | |
<xsl:variable name="depth"> | |
<xsl:call-template name="computeDepth"/> | |
</xsl:variable> | |
<xsl:copy> | |
<xsl:apply-templates select="@*|node()" mode="fixTable"> | |
<xsl:with-param name="depth" select="$depth + 1"/> | |
</xsl:apply-templates> | |
</xsl:copy> | |
</xsl:template> | |
<xsl:template match="table" mode="fixTable"> | |
<xsl:param name="depth"/> | |
<xsl:copy> | |
<xsl:attribute name="cols"> | |
<xsl:value-of select="$depth + 2"/> | |
</xsl:attribute> | |
<xsl:apply-templates select="@*" mode="fixTable"/> | |
<xsl:call-template name="cols"> | |
<xsl:with-param name="count" select="$depth - 1"/> | |
</xsl:call-template> | |
<col width="{normalize-space(20 - $depth)}%"/> | |
<xsl:apply-templates select="node()" mode="fixTable"> | |
<xsl:with-param name="depth" select="$depth"/> | |
</xsl:apply-templates> | |
</xsl:copy> | |
</xsl:template> | |
<xsl:template match="thead/tr/th[1]" mode="fixTable"> | |
<xsl:param name="depth"/> | |
<xsl:copy> | |
<xsl:attribute name="colspan"> | |
<xsl:value-of select="$depth"/> | |
</xsl:attribute> | |
<xsl:apply-templates select="@*|node()" mode="fixTable"> | |
<xsl:with-param name="depth" select="$depth"/> | |
</xsl:apply-templates> | |
</xsl:copy> | |
</xsl:template> | |
<xsl:template match="tbody[../table]" mode="fixTable"> | |
<xsl:param name="depth"/> | |
<xsl:copy> | |
<xsl:apply-templates select="@*|node()" mode="fixTable"> | |
<xsl:with-param name="depth" select="$depth"/> | |
</xsl:apply-templates> | |
</xsl:copy> | |
</xsl:template> | |
<xsl:template match="tbody[not(../table)]" mode="fixTable"> | |
<xsl:param name="depth"/> | |
<xsl:apply-templates select="@*|node()" mode="fixTable"> | |
<xsl:with-param name="depth" select="$depth - 1"/> | |
</xsl:apply-templates> | |
</xsl:template> | |
<xsl:template match="tbody/tr/th" mode="fixTable"> | |
<xsl:param name="depth"/> | |
<xsl:if test="../../tr[1] = .."> | |
<td rowspan='{count(../..//tr)}'> </td> | |
</xsl:if> | |
<xsl:copy> | |
<xsl:attribute name="colspan"> | |
<xsl:value-of select="$depth"/> | |
</xsl:attribute> | |
<xsl:apply-templates select="@*|node()" mode="fixTable"> | |
<xsl:with-param name="depth" select="$depth"/> | |
</xsl:apply-templates> | |
</xsl:copy> | |
</xsl:template> | |
<xsl:template match="@*|node()" mode="fixTable"> | |
<xsl:param name="depth"/> | |
<xsl:copy> | |
<xsl:apply-templates select="@*|node()" mode="fixTable"> | |
<xsl:with-param name="depth" select="$depth"/> | |
</xsl:apply-templates> | |
</xsl:copy> | |
</xsl:template> | |
</xsl:stylesheet> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment