Search code examples
xslt-2.0

XSLT - How to tell if an element with specific attribute value is the last in the matching series


I have the following XSLT template

<xsl:template match="xsd:element[@name != '' and not(starts-with(@type, 'common:'))]">
    <xsl:if test="position() != last()">
        "<xsl:value-of select="@name"/>", 
    </xsl:if>
    <xsl:if test="position() = last()">
        "<xsl:value-of select="@name"/>"
    </xsl:if>
</xsl:template>

that tries to match all elements with non empty name and it's type doesn't start with 'common:' then it will generate a comma separated list of these elements names.

so if applied to

<xsd:element name="One"     type="String"/>
<xsd:element name=""        type="String"/>
<xsd:OtherNode />
<xsd:element name="Two"     type="common:Characters"/>
<xsd:element name="Three"   type="Long"/>
<xsd:OtherNode />

it should generate

"One",
"Three"

notice that there is no comma after "Three"

but it sounds like there is something wrong with position() and last() as when printing its values it doesn't sounds to be correct and there is always a comma ','

"One",
"Three",

a complete sample of an input XML that will be processed by the XSLT is an XSD something like

<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns:xsd="[h t t p] ://www.w3.org/2001/XMLSchema"
           xmlns:common="[http]://abc.com/common/1.0">

    <!-- definition of simple elements -->
    <xsd:element name="orderperson" type="xsd:string"/>
    <xsd:element name="name"        type="xsd:string"/>
    <xsd:element name="address"     type="xsd:string"/>
    <xsd:element name="city"        type="xsd:string"/>
    <xsd:element name="country"     type="xsd:string"/>
    <xsd:element name="title"       type="xsd:string"/>
    <xsd:element name="note"        type="xsd:string"/>
    <xsd:element name="quantity"    type="xsd:positiveInteger"/>
    <xsd:element name="price"       type="common:decimal"/>

    <!-- definition of complex elements -->
      <xsd:complexType name="shipto">
        <xsd:sequence>
          <xsd:element ref="name"/>
          <xsd:element ref="address"/>
          <xsd:element ref="city"/>
          <xsd:element ref="country"/>
        </xsd:sequence>
      </xsd:complexType>

      <xsd:complexType name="item">
        <xsd:sequence>
          <xsd:element ref="title"/>
          <xsd:element ref="note" minOccurs="0"/>
          <xsd:element ref="quantity"/>
          <xsd:element ref="price"/>
        </xsd:sequence>
      </xsd:complexType>

      <xsd:complexType name="shiporder">
        <xsd:sequence>
          <xsd:element ref="orderperson"/>
          <xsd:element ref="shipto"/>
          <xsd:element ref="item" maxOccurs="unbounded"/>
        </xsd:sequence>
      </xsd:complexType>

</xsd:schema> 

and below is part of my XSLT after removing unrelated sections to keep it short

<xsl:stylesheet version="2.0" 
            xmlns:xsl="[http]://www.w3.org/1999/XSL/Transform"
            xmlns:xsd="[http]://www.w3.org/2001/XMLSchema"
            xmlns:xml="[http]://www.w3.org/XML/1998/namespace"
            xmlns:common="[http]://abc.com/common/1.0">
<xsl:output method="text" media-type="text/xml" indent="yes" encoding="ISO-8859-1" />

    <xsl:template match="/">
        <xsl:call-template name="pre-properties"/>
         <xsl:apply-templates/>
         <xsl:call-template name="post-properties"/>
    </xsl:template>

    <xsl:template match="/xsd:schema/xsd:complexType/xsd:sequence/xsd:element"/>

<xsl:template name="pre-properties">
    {
<xsl:template name="post-properties">
    }
</xsl:template>

<xsl:template match="xsd:element[@name != '' and not(starts-with(@type, 'common:'))]">
        <xsl:if test="position() != last()">
            "<xsl:value-of select="@name"/>", 
        </xsl:if>
        <xsl:if test="position() = last()">
            "<xsl:value-of select="@name"/>"
        </xsl:if>
</xsl:template>
</xsl:stylesheet>

Solution

  • Move the check into a predicate e.g.

    <xsl:template match="xsd:element[@name != '' and not(starts-with(@type, 'common:'))][position() != last()]">
    "<xsl:value-of select="@name"/>", 
    </xsl:template>
    
    <xsl:template match="xsd:element[@name != '' and not(starts-with(@type, 'common:'))][position() = last()]">
    "<xsl:value-of select="@name"/>"
    </xsl:template>
    

    Or you might simply try

    <xsl:value-of select="//xsd:element[@name != '' and not(starts-with(@type, 'common:'))]/concat('&quot;', @name, '&quot;')" separator=", "/>
    

    where you want to output those values.