xmlxsltapache-nifi

How to select a preceding sibling with an attribute value equal to current()/@value -1 in XSLT?


I'm new to XSLT and despite my research, I struggle to find explanations or at least to apply them to my case, so I try my luck here and thank you in advance for your attention

I have an XML structure where each element(units/results) has an attribute named packagingLevel. I need to find a preceding sibling of the current node where the packagingLevel attribute equals current()/@packagingLevel -1 and select the value of a different attribute (packagingType) within the transformation that I already have. So if the packagingLevel = 5, I'm searching the packagingType of a sibling that have packagingLevel = 4.

Here is an exemple of my input :

<root>
    <header>
        <products>
            <results>
                <units>
                    <results>
                        <itemCode>001</itemCode>
                        <packagingType>BOX</packagingType>
                        <eanCode>0000000000001</eanCode>
                        <packagingLevel>1</packagingLevel>
                        <quantity>1</quantity>
                    </results>
                    <results>
                        <itemCode>002</itemCode>
                        <packagingType>PALETTE</packagingType>
                        <eanCode>0000000000002</eanCode>
                        <packagingLevel>2</packagingLevel>
                        <quantity>1</quantity>
                    </results>
                </units>
            </results>
        </products>
    </header>
</root>

This is an exemple of what I tried :

<?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:template match="/">
        <xsl:for-each select="//products/results">
            <Element>
                <xsl:for-each select="units/results">
                    <UE>
                        <UE_EAN>
                            <xsl:value-of select="eanCode" />
                        </UE_EAN>
                        <TYPE_UE>
                            <xsl:choose>
                                <xsl:when test="packagingLevel = 1">A01</xsl:when>
                                <xsl:when test="packagingType = 'BOX'">B01</xsl:when>
                                <xsl:when test="packagingType = 'PALETTE'">P01</xsl:when>
                            </xsl:choose>
                        </TYPE_UE>

                        <TYPE_UE_CONTENT>
                            <xsl:choose>
                                <xsl:when test="packagingLevel = 1">A01</xsl:when>
                                <xsl:otherwise>
                                    <xsl:variable name="prevLevel" select="preceding-sibling::units[packagingLevel = current()/packagingLevel - 1]/TYPE_UE" />
                                    <xsl:value-of select="$prevLevel"/>
                                </xsl:otherwise>
                            </xsl:choose>
                        </TYPE_UE_CONTENT>

                        <UE_QTE>
                            <xsl:value-of select="quantite" />
                        </UE_QTE>
                    </UE>
                </xsl:for-each>
            </Element>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

here is the current output :

<Element>
    <... />
    <... />
    <UE>
        <UE_EAN>0000000000001</UE_EAN>
        <TYPE_UE>A01</TYPE_UE>
        <TYPE_UE_CONTENT>A01</TYPE_UE_CONTENT>
        <UE_QTY>1</UE_QTY>
    </UE>
    <UE>
        <UE_EAN>0000000000002</UE_EAN>
        <TYPE_UE>P01</TYPE_UE>
        <TYPE_UE_CONTENT />
        <UE_QTE>2</UE_QTE>
    </UE>
    <... />
</Element>

and here is the expected output based on the exemple above :

<Element>
    <... />
    <... />
    <UE>
        <UE_EAN>0000000000001</UE_EAN>
        <TYPE_UE>A01</TYPE_UE>
        <TYPE_UE_CONTENT>A01</TYPE_UE_CONTENT>
        <UE_QTY>1</UE_QTY>
    </UE>
    <UE>
        <UE_EAN>0000000000002</UE_EAN>
        <TYPE_UE>P01</TYPE_UE>
        <TYPE_UE_CONTENT>A01</TYPE_UE_CONTENT>
        <UE_QTE>2</UE_QTE>
    </UE>
    <... />
</Element>

Thanks a lot for reading me and any help or explanation you could provide me !


Solution

  • You cannot use the preceding-sibling axis to retrieve a calculated value from the output. You must either re-calculate the value using the values from the input or (more efficiently) pre-calculate all values before writing to the output.

    In XSLT 1.0 this would be done as:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="/root">
        <xsl:for-each select="header/products/results">
    
            <xsl:variable name="temps">
                <xsl:for-each select="units/results">
                    <temp>
                        <xsl:copy-of select="*"/>
                        <TYPE_UE>
                            <xsl:choose>
                                <xsl:when test="packagingLevel = 1">A01</xsl:when>
                                <xsl:when test="packagingType = 'BOX'">B01</xsl:when>
                                <xsl:when test="packagingType = 'PALETTE'">P01</xsl:when>
                            </xsl:choose>
                        </TYPE_UE>
                    </temp>
                </xsl:for-each>
            </xsl:variable>
    
            <Element>
                <xsl:for-each select="exsl:node-set($temps)/temp">
                    <UE>
                        <UE_EAN>
                            <xsl:value-of select="eanCode"/>
                        </UE_EAN>
                        <xsl:copy-of select="TYPE_UE"/>
                        <TYPE_UE_CONTENT>
                            <xsl:choose>
                                <xsl:when test="packagingLevel = 1">A01</xsl:when>
                                <xsl:otherwise>
                                    <xsl:value-of select="preceding-sibling::temp[packagingLevel = current()/packagingLevel - 1]/TYPE_UE"/>
                                </xsl:otherwise>
                            </xsl:choose>
                        </TYPE_UE_CONTENT>
                        <UE_QTY>
                            <xsl:value-of select="quantity"/>
                        </UE_QTY>
                    </UE>
                </xsl:for-each>
            </Element>  
    
        </xsl:for-each>
    </xsl:template>
    
    </xsl:stylesheet>