Re: [xsl] how do you count based on the number of preceding attributes?

Subject: Re: [xsl] how do you count based on the number of preceding attributes?
From: "Tony Zanella" <tony.zanella@xxxxxxxxx>
Date: Sat, 7 Jun 2008 14:48:27 -0400
I've just got it to work! The heart of the stylesheet is:

    <xsl:template match="@id[contains(.,'div')]">
        <xsl:variable name="test_value"><xsl:number level="any"
count="*[contains(@id, 'div')]"/></xsl:variable>

        <xsl:attribute name="id"  select="replace(., '[0-9]+$', $test_value)"/>
    </xsl:template>

</xsl:stylesheet>

and it works beautifully, not just on the test data, but on the "real
world" xml, too!  Thanks so much for pointing me in the right
direction!
Tony Zanella

On Fri, Jun 6, 2008 at 10:07 PM, Tony Zanella <tony.zanella@xxxxxxxxx> wrote:
> Dear Dr. Kay and Readers,
> Implementing the changes suggested below as follows, on this markup:
>
> <?xml version="1.0" encoding="UTF-8"?>
> <root>
>    <div0 type="heading" id="abc.div.1.2.3">
>        <div1/><div1 type="article" id="abc.div.1.2.6">
>            <div2 type="subsection" id="abc.div.1.2.1"/>
>            <pb n="1" id="abc.1.2.2"/>
>        </div1>
>    </div0>
> </root>
>
> With the sylesheet now reading:
>
> <?xml version="1.0" encoding="UTF-8"?>
> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="2.0">
>
>    <xsl:template match="@*|node()">
>        <xsl:copy>
>            <xsl:apply-templates select="@*|node()"/>
>        </xsl:copy>
>    </xsl:template>
>
>    <xsl:template match="@id[contains(.,'div')]">
>        <xsl:variable name="test_value"
> select="string(count(preceding::*[contains(@id, 'div')]))"/>
>        <xsl:attribute name="id"  select="replace(., '[0-9]+$', $test_value)"/>
>    </xsl:template>
>
> </xsl:stylesheet>
>
> I get the output:
>
> <?xml version="1.0" encoding="UTF-8"?><root>
>    <div0 type="heading" id="abc.div.1.2.0">
>        <div1/><div1 type="article" id="abc.div.1.2.0">
>            <div2 type="subsection" id="abc.div.1.2.0"/>
>            <pb n="1" id="abc.1.2.2"/>
>        </div1>
>    </div0>
> </root>
>
> The output I want is:
> <?xml version="1.0" encoding="UTF-8"?><root>
>    <div0 type="heading" id="abc.div.1.2.1">
>        <div1/><div1 type="article" id="abc.div.1.2.2">
>            <div2 type="subsection" id="abc.div.1.2.3"/>
>            <pb n="1" id="abc.1.2.2"/>
>        </div1>
>    </div0>
> </root>
>
> Any further suggestions?  (If it helps to hear: the test case
> represents a file where the last integers in the div @id attributes
> are misnumbered, and the goal of the stylesheet is to number them
> sequentially...the pb element has an @id attribute, but it's not my
> concern here...).
> Thanks for your help and patience with my slow learning process,
> Tony Zanella
>
>
>
>
> On Fri, Jun 6, 2008 at 4:47 PM, Michael Kay <mike@xxxxxxxxxxxx> wrote:
>>> Using xsl processor: Saxon 8B, here's my test data:
>>>
>>> <?xml version="1.0" encoding="UTF-8"?>
>>> <root>
>>>     <div0 type="heading" id="abc.div.1.2.3">
>>>         <div1 type="article" id="abc.div.1.2.6">
>>>             <div2 type="subsection" id="abc.div.1.2.1"/>
>>>             <pb n="1" id="abc.1.2.2"/>
>>>             <div2 type="subsection" id="abc.div.1.2.64"/>
>>>         </div1>
>>>     </div0>
>>> </root>
>>>
>>> Here's the stylesheet I'm working on:
>>>
>> [identity template]+
>>>
>>>     <xsl:template match="*/@id[contains(.,'div')]">
>>
>> The "*/" adds nothing (well, it says "only match id attributes if they have
>> an element as a parent"). You can drop it.
>>>
>>>         <xsl:variable name="substring"
>>> select="substring-after(substring-after(substring-after(substr
>>> ing-after(.,'.'),'.'),'.'),'.')"/>
>>
>> You don't seem to use this variable and I don't think you need it.
>>
>>>         <xsl:variable name="test_value"><xsl:value-of
>>> select="count(preceding::node())"/></xsl:variable>
>>
>> NEVER write variables like this. It should be
>>
>> <xsl:variable name="test_value" select="string(count(preceding::node()))"/>
>>
>> You don't want a document node containing a text node; you want the integer,
>> converted to a string.
>>
>>> I want the value of $test_value to be, for example, 1 if the @id
>> containing the string 'div' is the first @id, 2 if it is the second, and so
>> on.
>>
>> You haven't made it clear whether you want to count all @id attributes, or
>> only those that contain 'div'. In the first case it's
>> count(preceding::*[@id]), in the second it's
>> count(preceding::*[contains(@id, 'div')]).
>>
>> Alternatively, use <xsl:number level="any" count="*[@id]"/>
>>
>>>         <xsl:attribute name="id"
>>> select="concat(replace(.,'([a-z]{3})\.div\.([0-9]+)\.([0-9]+)\
>>> .([0-9]+)','$1.div.$2.$3.'),$test_value)"/>
>>
>> The seems very cumbersome. To replace the last number in the string, use
>>
>> replace(., '[0-9]+$', $test_value)
>>
>> Michael Kay
>> http://www.saxonica.com/

Current Thread