1

I have an XML document full of ICD10 codes formatted like this:

<ICD10CM.index>
    <version>2019</version>
    <title>ICD-10-CM INDEX TO DISEASES and INJURIES</title>
    <letter>
        <title>A</title>
        <mainTerm>
            <title>Abnormal, abnormality, abnormalities</title>
            <seeAlso>Anomaly</seeAlso>
            <term level="1">
                <title>chromosome, chromosomal</title>
                <code>Q99.9</code>
                <term level="2">
                    <title>with more than three X chromosomes,  female</title>
                    <code>Q97.1</code>
                </term>
                <term level="2">
                    <title>analysis result</title>
                    <code>R89.8</code>
                    <term level="3">
                        <title>bronchial washings</title>
                        <code>R84.8</code>
                    </term>
                    <term level="3">
                        <title>cerebrospinal fluid</title>
                        <code>R83.8</code>
                    </term>
                </term>
            </term>
        </mainTerm>
    </letter>
</ICD10CM.index>

I would like to end up with a collapsed list of items, but each item in the final list needs to reference its parent(s), potentially recursively. I would like the output to look something like this:

<codes>
    <code>
        <id>Q99.9</id>
        <description>Chromosome, chromosomal</description>
    </code>
    <code>
        <id>Q97.1</id>
        <description>Chromosome, chromosomal – with more than three X chromosomes, female</description>
    </code>
    <code>
        <id>R89.8</id>
        <description>Chromosome, chromosomal – analysis result</description>
    </code>
    <code>
        <id>R84.8</id>
        <description>Chromosome, chromosomal – analysis result – bronchial washings</description>
    </code>
    <code>
        <id>R83.8</id>
        <description>Chromosome, chromosomal – analysis result – cerebrospinal fluid</description>
    </code>
</codes>

I'm fairly new to XSLT and have only built a handful of transforms, so huge bonus points for helping me understand the answer instead of just providing a code snippet that does the job.

This level="3" example is the most deeply-nested sample I found in a quick search of the data but I'd prefer an example that works for any depth.

Edit: Since folks have asked, I am using XSLT 2.0

4
  • Please state if you're using XSLT 1.0 or 2.0 (or 3.0). Commented Mar 13, 2019 at 8:36
  • Also, why do some descriptions in your expected output include the id? Commented Mar 13, 2019 at 8:48
  • Not sure your overall goal here, but my CRAN 'icd' package for R already does this for the whole ICD-10-CM XML definitions, and has pre-compiled data frames which might be usable directly, as well as a range of tools for using these codes for good. Commented Mar 13, 2019 at 12:39
  • Apologies for the typos including IDs in my output incorrectly and for not specifying a version, edits have been made. Commented Mar 13, 2019 at 19:57

2 Answers 2

3

I believe it can be simply:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/ICD10CM.index">
    <codes>
        <xsl:apply-templates select="letter/mainTerm/term[@level='1']"/>
    </codes>
</xsl:template>

<xsl:template match="term">
    <code>
        <id>
            <xsl:value-of select="code" />
        </id>
        <description>
            <xsl:for-each select="ancestor::term">
                <xsl:value-of select="title" />
                <xsl:text> - </xsl:text>
            </xsl:for-each>
            <xsl:value-of select="title" />
        </description>
    </code>
    <xsl:apply-templates select="term"/>
</xsl:template>

</xsl:stylesheet>

The code should be self-explanatory - if not, ask.


If you can use XSLT 2.0, you could shorten this to:

XSLT 2.0

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/ICD10CM.index">
    <codes>
        <xsl:apply-templates select="letter/mainTerm/term[@level='1']"/>
    </codes>
</xsl:template>

<xsl:template match="term">
    <code>
        <id>
            <xsl:value-of select="code" />
        </id>
        <description>
            <xsl:value-of select="ancestor-or-self::term/title" separator=" - "/>
        </description>
    </code>
    <xsl:apply-templates select="term"/>
</xsl:template>

</xsl:stylesheet>

Note that this could be made a bit more efficient by passing down the accumulated description as a parameter, instead of traversing up the entire ancestor axis for each term:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/ICD10CM.index">
    <codes>
        <xsl:apply-templates select="letter/mainTerm/term[@level='1']"/>
    </codes>
</xsl:template>

<xsl:template match="term">
    <xsl:param name="accumulated-description"/>
    <xsl:variable name="description" select="concat($accumulated-description, title)" />
    <code>
        <id>
            <xsl:value-of select="code" />
        </id>
        <description>
            <xsl:value-of select="$description" />
        </description>
    </code>
    <xsl:apply-templates select="term">
        <xsl:with-param name="accumulated-description" select="concat($description, ' - ')"/>
    </xsl:apply-templates>
</xsl:template>

</xsl:stylesheet>
Sign up to request clarification or add additional context in comments.

2 Comments

Your XSLT 2.0 example works perfectly - one final question though, what if I wanted to include the <title> of the <mainTerm> in the output as well?
Where exactly do you want to include it?
0

One of the solution in XSLT 1.0 can be:

Note: I have tried to explain at each level via comments what the template or loop does. Let me know if I am missing something.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />

<xsl:variable name="dash"> - </xsl:variable>

<xsl:template match="/">
    <codes>
        <xsl:apply-templates />
    </codes>
</xsl:template>

<!-- It matches all the <code> elements from the document -->
<xsl:template match="code">
    <code>
        <!-- Creates <id> for each <code> and puts the value of 'code int it'-->
        <id><xsl:value-of select="." /></id>

        <!-- Creates <description> tag and puts required values described below -->
        <description>
            <!-- Iterates through all <title> elements whose parent node is 'term' and who itself is preceding-sibling of 'term' 
            and gets the value of all such 'title''-->
            <xsl:for-each select="ancestor::term/preceding-sibling::title[name(parent::node()) = 'term']">
                <xsl:value-of select="normalize-space(.)" />
                <!-- It will join strings by '-' until the last element found -->
                <xsl:if test="position() != last()">
                    <xsl:value-of select="$dash" />
                </xsl:if>
            </xsl:for-each>

            <!-- Appends the value of 'title' who is preceding-sibling of current matching 'code' -->
            <xsl:choose>
                <!-- If it is first <code> in the tree than it won't concat '-' -->
                <xsl:when test="not(ancestor::term/preceding-sibling::title[name(parent::node()) = 'term'])">
                    <xsl:value-of select="preceding-sibling::title" />
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="concat($dash, preceding-sibling::title)" />
                </xsl:otherwise>
            </xsl:choose>
        </description>
    </code>
</xsl:template>

<xsl:template match="text()" />

</xsl:stylesheet>

http://xsltransform.net/93wkLJ4/1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.