Search code examples
xsltxslkey

Use XSLT key() function to lookup nodes based on two attributes in separate elements


I'm trying to use the XSLT key() function to return all the <Code> elements in an XML file that match the following two criteria:

Code[code=$code] AND ancestor::CodeType[type=$codeType]`


Here is a simplified example of what the input XML looks like:

<Items>
    <Item code-number="C1" category="ABC" />
    <Item code-number="C3" category="ABC" />
    <Item code-number="C1" category="XYZ" />
</Items>

<CodeTypes>
    <CodeType type="ABC">
        <SubType title="Category III Codes">   <!-- <SubType> elements are optional -->
            <SubType title="Subcategory III-15 Codes">
                <Code code="C1" description="Red" />
                <Code code="C2" description="Green" />
                <Code code="C3" description="Blue" />
                <Code code="C3" description="Purple" />   <!-- Same code can appear more than once -->
            </SubType>
        </SubType>
    <CodeType>
    <CodeType type="XYZ">
        <Code code="C1" description="Black" />   <!-- Same code can be used for multiple CodeTypes -->
        <Code code="C2" description="Orange" />
        <Code code="C3" description="Yellow" />
    <CodeType>
</CodeTypes>

Note that the comments aren't actually there in the actual XML, I'm just adding them here to clarify the XML structure.


Here is the XSLT transform I am trying to use, though it doesn't seem to be working:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="LookupMatchingCodeElements" match="Code" use="concat(../@code, '+', ancestor::CodeType/@type)" />

    <xsl:template match="Item">
        <xsl:call-template name="GetCodeElements">
            <xsl:with-param name="code" select="@code-number" />
            <xsl:with-param name="codeType" select="@category" />
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="GetCodeElements">
        <xsl:param name="code" />
        <xsl:param name="codeType" />

        <xsl:for-each select="key('LookupMatchingCodeElements', concat($code, '+', $codeType))">
            <!-- process each <Code> element -->
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>


And this is what I want the key() function to return with different inputs:

<!-- For code="C1" AND codeType="ABC" -->
<Code code="C1" description="Red" />

<!-- For code="C3" AND codeType="ABC" -->
<Code code="C3" description="Blue" />
<Code code="C3" description="Purple" />

<!-- For code="C1" AND codeType="XYZ" -->
<Code code="C1" description="Black" />


Is this possible with the key() function?  As there are hundreds of thousands of both <Item> and <Code> elements, being able to use <xsl:key> is very important.


Solution

  • Use:

     <xsl:key name="kCode" match="Code"
      use="concat(ancestor::CodeType[1]/@type, '+', @code)"/>
    

    Here is a complete transformation:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
     <xsl:key name="kCode" match="Code"
      use="concat(ancestor::CodeType[1]/@type, '+', @code)"/>
    
     <xsl:template match="/">
      <xsl:copy-of select="key('kCode', 'ABC+C1')"/>
    ====================
      <xsl:copy-of select="key('kCode', 'ABC+C3')"/>
    ====================
      <xsl:copy-of select="key('kCode', 'XYZ+C1')"/>
     </xsl:template>
    </xsl:stylesheet>
    

    When this transformation is applied on the following document (the provided XML text -- corrected malformedness):

    <t>
        <Items>
            <Item code-number="C1" category="ABC" />
            <Item code-number="C3" category="ABC" />
            <Item code-number="C1" category="XYZ" />
        </Items>
        <CodeTypes>
            <CodeType type="ABC">
                <SubType title="Category III Codes">
                    <!-- <SubType> elements are optional -->
                    <SubType title="Subcategory III-15 Codes">
                        <Code code="C1" description="Red" />
                        <Code code="C2" description="Green" />
                        <Code code="C3" description="Blue" />
                        <Code code="C3" description="Purple" />
                        <!-- Same code can appear more than once -->
                    </SubType>
                </SubType>
            </CodeType>
            <CodeType type="XYZ">
                <Code code="C1" description="Black" />
                <!-- Same code can be used for multiple CodeTypes -->
                <Code code="C2" description="Orange" />
                <Code code="C3" description="Yellow" />
            </CodeType>
        </CodeTypes>
    </t>
    

    the wanted, correct result is produced:

    <Code code="C1" description="Red"/>
    ====================
      <Code code="C3" description="Blue"/>
    <Code code="C3" description="Purple"/>
    ====================
      <Code code="C1" description="Black"/>