Select questions

1. Matching based on a range of values
2. Conditional selection of an attribute value
3. Matching an element with one of n attribute values
4. Unique attribute values in XSLT2
5. Boolean tests in 2.0
6. Tabular alternating colours
7. Select a range of nodes
8. Add default attributes.
9. Listing terms selectively
10. Unique attribute values

1.

Matching based on a range of values

Michael Kay



> "Select all nodes where current nodes @timebeg falls between the 
> @timebeg and @timeend of all event1 elements".

You can do this in XPath 2 as

//node[every $n in //node[@id=1] satisfies 
        (@timebeg ge $n/@timebeg and @timebeg le $n/@timeend)]

You can't do general joins in XPath 1 (it's not relationally complete). The nearest you can get is

<xsl:for-each select="//node">
  <xsl:variable name="n" select="."/>
  <xsl:copy-of select="$n[not(//node[@id=1][@timebeg &lt;= $n/@timebeg or 
                                            @timeget &gt;= $n/@timeend)]"/>
</xsl:for-each>

2.

Conditional selection of an attribute value

Michael Kay



> choose a link for the title, based on the following conditions:
>   1. if the value of the link node has 'http://' string
>   2. if there's no 'http://' string get the value of the link node 
> that contains 'ftp://' string

> output should be: <a href="selected link">title</a>
> <?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?> <record>  
> <data>
>   <link>http://www.link1.com</link>
>   <link>3csbv</link>
>   <link>ftp://link2.com</link>
>   <link>http://www.link3.com</link>
>   <title>title</title>
>  </data>
>  <data>
>   <link>45csgh</link>
>   <link>invalid link</link>
>   <link>ftp://link1.com</link>
>   <title>title</title>
>  </data>
> </record>
>

<xsl:template match="data">
<a href="{(link[matches(.,'^http://')],link[matches(.,'^ftp://')])[1]}">
<xsl:value-of select="title"/>
</a>
</xsl:template>

Ednote. Note the use of braces inside the AVT; and the use of the matches function in the predicate.

3.

Matching an element with one of n attribute values

David Carlisle


> I want to do something like <xsl:template match="volume/book[chapter/@title in ('monkeys','buffalo')]">
> So in this example I would want to match any book that had a chapter titled 'monkeys' or 'buffalo'.
>
>I realize I can do this, but this will get very tedious...
>
>< xsl:template match="volume/book[chapter/@title = 'monkeys'] | 
                          volume/book[chapter/@title = 'buffalo']"/>

Try

<xsl:template match="volume/book[chapter/@title=('monkeys','buffalo')]">

4.

Unique attribute values in XSLT2

You just need

<xsl:variable name="x" 
 select="distinct-values(/A/B/C/@atc)"/>

5.

Boolean tests in 2.0

Andrew Welsh



 Can I use a boolean variable in an xsl:if test

beware though that that will get you burned again when you start using XSLT2 scented water.

xsl:value-of returns a text node with string value the string value of the expression. This is subtly or not so subtly different from a string. It doesn't make so much difference in XSLT1 as the only way to carry strings around is to put them in text nodes, but in xpath2 you can have sequences of strings and sequences of text nodes (and sequences that contain both strings and text nodes) the rules for the two cases (and in particular whether spaces are automatically inserted between adjacent items) are different on the two cases.

Yes, but I think the 2.0 way makes more sense.

Just to make sure we are talking about the same thing (and to help cement my knowledge), consider:

<root>
  <node>foo</node>
  <node>bar</node>
</root>

In 1.0:

<xsl:template match="root">
  <xsl:value-of select="node"/>
</xsl:template>

Returns:

'foo'

Because in XSLT 1.0 'first item semantics' apply when a value-of is performed on a sequence.

In 2.0 the same template would return:

'foo bar'

That is, all items in the sequence with a single space as a seperator. In order to remove/control the space, we can use the @separator on value-of:

<xsl:value-of select="node" separator=""/>

Which would produce:

'foobar'

For me, that's much more intuitive than just picking the first one. Another plus for 2.0 :)

Of course, if there is another sequence related area to get burned on please post an example - it's good to know the gotchas up front.

6.

Tabular alternating colours

Michael Kay


> Hi, I'm trying implementing a XSL which distinguishes odd and 
> even row 
> of a table by using different colors. 

Another solution to add to the list you've been given: In XSLT 2.0 you can do

<td bgcolor="{if (position() mod 2) then 'black' else 'white'}">
  ...
</td>

7.

Select a range of nodes

Michael Kay


 Is it possible to use Xpath to select a range of nodes.
>
> <chapter>
> <title>X</title>
> <para></para>,
> <para></para>,
> <title>Y</title>
> </chapter>
>
> I would like an Xpath statement that would select //title[1]
> THROUGH //title[2] and include all nodes between.  Is this possible?
>

I'm assuming that <para> represents <para>....</para>, i.e. a complete element.

If you know that the nodes are siblings, and you are positioned on their parent, then you can do

(title[1] , *[. >> title[1] and . << title[2]] , title[2])

If they aren't siblings and you are positioned on the root, then you can do

for $T1 in (//title)[1], $T2 in (//title)[2]
return ($T1, //*[. >> $T1 and . << $T2], $T2)

8.

Add default attributes.

George Cristian Bina




> I'm trying to add default attribute values to elements, but I get
> errors. Is there a simple way to do it using xpath? What i'm trying is:
>
> <input type="text" value="{un|'Please enter your username'}" />
>
> Is there a simple way of doing this, or do I have to throw an xsl:choose
> inside an xsl:attribute?

In XSLT 2.0 you can write

value="{(un,'Please enter your username')[1]}"

which selects the first from the set, which is what you want.

9.

Listing terms selectively

Andrew Welch


> <areaserved>
>         <district name="South Hams">
>                 <town name="Dartmouth">
>                         <settlement name="Kingswear"/>
>                 </town>
>                 <town name="Totnes"/>
>         </district>
>         <district name="Torbay"/>
> </areaserved>
>
> I am trying to write some XSLT to transform this to a list as follows:
>
> Kingswear, Totnes, Torbay

In 2.0 you can use:

string-join(//*[not(*)]/@name, ', ')

10.

Unique attribute values

David Carlisle


I have an XML like this:

      <target:row Category=3D'A-B-C-D'/>
      <target:row Category=3D'A-B'/>
      <target:row Category=3D'A-C-D'/>
      <target:row Category=3D'C'/>
      <target:row Category=3D'B-C-D'/>
      <target:row Category=3D'A'/>

with a combination of values separated by '-'. I need to populate 
a listbox with unique values:

A
B
C
D

David C offers

<x xmlns:target="t">

      <target:row Category='A-B-C-D'/>
      <target:row Category='A-B'/>
      <target:row Category='A-C-D'/>
      <target:row Category='C'/>
      <target:row Category='B-C-D'/>
      <target:row Category='A-t'/>
</x>

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; xmlns:target="t" >

<xsl:output indent="yes"/>

<xsl:template match="x">
 <xsl:for-each select="distinct-values(target:row/tokenize(@Category,'-'))">
  <option><xsl:value-of select="."/></option>
 </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

$ saxon8 spl.xml spl.xsl
<?xml version="1.0" encoding="UTF-8"?>
<option xmlns:target="t">A</option>
<option xmlns:target="t">B</option>
<option xmlns:target="t">C</option>
<option xmlns:target="t">D</option>
<option xmlns:target="t">t</option>

Florent Georges offers

or (I prefer this one):

   distinct-values(target:row/tokenize(@Category, '-'))