Question

In the following code, I need, in the place of the two xxxx's, to have the name of the element (name()), so h1, h2 or h3, whatever the match may be. So the second xxxx must be the count of the h1/h2/h3 in that file. The attribute will then look like "h1_4", or h3_15" etc.

How do I do that ?

<xsl:template match="h1[not(@id)] | h2[not(@id)] | h3[not(@id)]" >
   <xsl:element name="{name()}" >
     <xsl:attribute name="id">xxxx_<xsl:value-of><xsl:number count="xxxx" /></xsl:value-of></xsl:attribute>
   </xsl:element>
  <xsl:apply-templates/> 
</xsl:template>
Was it helpful?

Solution

As I said, the request is ambiguous. The following stylesheet:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="h1[not(@id)] | h2[not(@id)] | h3[not(@id)]" >
<xsl:variable name="name" select="name()" />
    <xsl:copy>
           <xsl:attribute name="id">
               <xsl:value-of select="$name"/>
               <xsl:text>_</xsl:text>
               <xsl:value-of select="count(preceding::*[name()=$name]) + 1"/>
           </xsl:attribute>
           <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

when applied to the following test input:

<root>
    <h1 id="h1_1"/>
    <h2 type="abc"/>
    <h3 type="xyz"/>
    <h1>content</h1>
    <h3 id="h3_2" type="efg"/>
    <h2/>
</root>

will produce:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <h1 id="h1_1"/>
   <h2 id="h2_1" type="abc"/>
   <h3 id="h3_1" type="xyz"/>
   <h1 id="h1_2">content</h1>
   <h3 id="h3_2" type="efg"/>
   <h2 id="h2_2"/>
</root>

OTHER TIPS

You're on the right track using xsl:number, how about:

<xsl:template match="h1 | h2 | h3" >
   <xsl:copy>
     <xsl:attribute name="id">
       <xsl:value-of select="name()"/>
       <xsl:text>_</xsl:text>
       <xsl:number level="any" />
     </xsl:attribute>
    <xsl:apply-templates select="@*|node()"/> 
   </xsl:copy>
</xsl:template>

I'm assuming an identity template to copy the rest of the document as-is, and that being the case I've simplified the match pattern - you don't need to check for not(@id) as where there is an id attribute in the input it will overwrite the one being created by the xsl:attribute.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top