In XSL, how can I return another element when its sibling element's attribute is a certain value?

Viewed 46

With an XML input like:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <loop Id="N1">
        <segment Id="N1">
            <element Id="N101">BT</element>
            <element Id="N102">JOHN'S PIZZA</element>
            <element Id="N103">92</element>
            <element Id="N104">910</element>
        </segment>
    </loop>
</root>

... when <element Id="N101">BT</element> exists, I am trying to get the value "JOHN'S PIZZA" from its related sibling <element Id="N102">JOHN'S PIZZA</element>.

I tried this to no avail...

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:template match="/root">
        <xsl:choose>
            <xsl:when test="loop[@Id = 'N1']/segment[@Id = 'N1']/element[@Id = 'N101'] = 'BT'">
                <BillTo>
                    <Name xml:lang="en-US">
                        <xsl:value-of select="../element[@Id = 'N102']"/>
                    </Name>
                </BillTo>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

... as it does not return any value:

<?xml version="1.0" encoding="UTF-8"?>
<BillTo>
   <Name xml:lang="en-US"/>
</BillTo>

Follow-up question...

How do I also return these other <element Id="N201"> elements when they are within the same loop[@Id = 'N1'] where <element Id="N101">BT</element> exists?

<root>
    <loop Id="N1">
        <segment Id="N1">
            <element Id="N101">BT</element>
            <element Id="N102">JOHN'S PIZZA</element>
            <element Id="N103">92</element>
            <element Id="N104">910</element>
        </segment>
  <segment Id="N2">
            <element Id="N201">SOME OTHER VALUE</element>
        </segment>
    </loop>
</root>

Thank you for any assistance.

1 Answers

You are inside the template matching /root and though you have tested a condition in your xsl:when, the context has not changed. You are still "standing" on the /root element. So, ../element[@Id = 'N102'] is attempting to jump up to the parent of root and then select the element, which does not exist.

I would adjust the XPath slightly to select the element you are trying to target:

loop[@Id = 'N1']//segment[@Id = 'N1' and element[@Id = 'N101' and . = 'BT']]/element[@Id = 'N102']

and use xsl:apply-templates, with a template to match the element and format it.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:template match="/root">
        <xsl:apply-templates select="loop[@Id = 'N1']//segment[@Id = 'N1' and element[@Id = 'N101' and . = 'BT']]/element[@Id = 'N102']"/>
    </xsl:template>
    
    <xsl:template match="element">
        <BillTo>
            <Name xml:lang="en-US">
                <xsl:value-of select="."/>
            </Name>
        </BillTo>
    </xsl:template>
</xsl:stylesheet>

But you could also use xsl:for-each to select that node, and then the context would be that selected element:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:template match="/root">
        <xsl:for-each select="loop[@Id = 'N1']//segment[@Id = 'N1' and element[@Id = 'N101' and . = 'BT']]/element[@Id = 'N102']">
            <BillTo>
                <Name xml:lang="en-US">
                    <xsl:value-of select="."/>
                </Name>
                <xsl:copy-of select="preceding-sibling::* | following-sibling::*"/>
            </BillTo>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>
Related