Nina
Nina

Reputation: 13

XSLT condition output one by one

I need to have in each 'a', all the 'b' that have @n greater than or equal to the @n of the 'a' in which they are contained.

I am using XSLT 2.0 and Saxon-HE 9.6.0.5


XML source:

<blabla>
    <a n="2"></a>
    <a n="6"></a>
    <b n="6"></b>
    <b n="1"></b>
    <b n="4"></b>
</blabla>

XSLT:

<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:template match="blabla">
    <all>
        <xsl:for-each select="//a">
            <a>
                <xsl:attribute name="n" select="./@n"/>
                <xsl:for-each select="//b">
                    <xsl:if test="./@n[. >= //a/@n]">
                        <b>
                            <xsl:attribute name="n" select="./@n"/>
                        </b>
                    </xsl:if>
                </xsl:for-each>
            </a>
        </xsl:for-each>
    </all>
</xsl:template>

What I would like to have is:

<all>
    <a n="2">
        <b n="6"/>
        <b n="4"/>
    </a>
    <a n="6">
        <b n="6"/>
    </a>
</all>

What I have instead is:

<all>
    <a n="2">
        <b n="6"/>
        <b n="4"/>
    </a>
    <a n="6">
        <b n="6"/>
        <b n="4"/>
    </a>
</all>

I am not sure if the whole approach is wrong or if I have to adjust something.

Just for completeness, this is the function with which I was trying to do the same thing. The output is NOTHING when I create elements inside the function:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:foo="http://whatever">

<xsl:function name="foo:test">
    <xsl:param name="a"/>
    <xsl:param name="b"/>
    <xsl:for-each select="$a">
        <a>
            <xsl:attribute name="n">
                <xsl:value-of select="$a"/>
            </xsl:attribute>
            <xsl:if test="$b >= $a">
                <b>
                    <xsl:attribute name="n">
                        <xsl:value-of select="$b"/>
                    </xsl:attribute>
                </b>
            </xsl:if>
        </a>
    </xsl:for-each>
</xsl:function>

<xsl:template match="/">
    test 1: <xsl:value-of select="foo:test(//a/@n, //b/@n)"/>
    test 2: <xsl:value-of select="foo:test(7, 6)"/>
    test 3: <xsl:value-of select="foo:test(3, 6)"/>
</xsl:template>

The same function, without creating elements, works fine with numbers, but not if I put as parameters the xpath expression that match the source document (it outputs everything).

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:foo="http://whatever">

<xsl:function name="foo:foo:test">
    <xsl:param name="a"/>
    <xsl:param name="b"/>
        <xsl:if test="$b >= $a">
        a: <xsl:value-of select="$a"/>
        b: <xsl:value-of select="$b"/>
        </xsl:if>   
</xsl:function>

<xsl:template match="/">
    test 1: <xsl:value-of select="foo:test(//a/@n, //b/@n)"/>
    test 2: <xsl:value-of select="foo:test(7, 6)"/>
    test 3: <xsl:value-of select="foo:test(3, 6)"/>
</xsl:template>

</xsl:stylesheet>

Output:

    test 1: 
        a: 2 6
        b: 6 1 4
    test 2: 
    test 3: 
        a: 3
        b: 6

I don't need to do it with a function; if you have suggestions for doing it without a function, it is fine.

Upvotes: 1

Views: 137

Answers (2)

Parker
Parker

Reputation: 7519

Here is an XSL 1.0 solution (also works with XSL 2.0 and is compatible with any XSL processor) that uses multiple templates and parameter passing.

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

  <xsl:output omit-xml-declaration="no" indent="yes" />

  <xsl:template match="/blabla">
    <all>
      <xsl:apply-templates select="a" mode="level-1">
        <xsl:sort data-type="number" order="ascending" select="@n" />
      </xsl:apply-templates>
    </all>
  </xsl:template>

  <xsl:template match="a" mode="level-1">
    <xsl:variable name="threshold" select="@n" />
    <a>
      <xsl:attribute name="n"><xsl:value-of select="$threshold" /></xsl:attribute>
      <xsl:apply-templates select="//b" mode="level-2">
        <xsl:with-param name="threshold" select="$threshold" />
      </xsl:apply-templates>
    </a>
  </xsl:template>

  <xsl:template match="b" mode="level-2">
    <xsl:param name="threshold" />
    <xsl:if test="number(@n) &gt;= number($threshold)">
      <b>
        <xsl:attribute name="n"><xsl:value-of select="@n" /></xsl:attribute>
      </b>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Michael Kay
Michael Kay

Reputation: 163360

Try:

<xsl:template match="blabla">
    <all>
        <xsl:for-each select="a">
            <a n="{@n}">
                <xsl:copy-of select="../b[@n >= current()/@n]"/>
            </a>
        </xsl:for-each>
    </all>
</xsl:template>

Upvotes: 1

Related Questions