1. | element-available() question |
| Michael Kay
The specification says that element-available() should return true if an
instruction of the given name is available. Saxon is taking the
specification literally: "xsl:template" is not an instruction. Other
processors are taking the name "element available" at its face value, and
returning true because there is an element available with that name.
|
2. | How do I obtain the content of a linked element |
| Sebastian R: I was delighted to find that id() function.
It meant that from <code id="GB">Great Britain</code>
..
<nat idref="GB"/>
I could get the <nat> to generate "Great Britain" with just
<xsl:apply-templates select="id(@idref)"/>
|
3. | Strange problem with string-replace |
| David Carlisle
> But when I want to remove
> all (line feed) or   (space)
line endings are normalised to 10 not 13, and since this is
working at the level of single characters you can use
translate() rather than string-replace.
<xsl:template match="a">
<xsl:value-of select="translate(.,' ','')"/>
</xsl:template>
</xsl:stylesheet>
converts
<a>
1 2
3 4
5 6
</a>
to
123456
|
4. | how to count equal nodes into one result element |
| Juliane Harbarth
Expansion of Question:
This is the source tree:
<count_this>
<tag>apple</tag>
<tag>apple</tag>
<tag>apple</tag>
<tag>banana</tag>
<tag>banana</tag>
<tag>cherry</tag>
<tag>cherry</tag>
<tag>cherry</tag>
<tag>cherry</tag>
</count_this>
The result tree should be something like this:
<result>
<tag><value>apple</value>
<occurred>3</occurred></tag>
<tag><value>banana</value>
<occurred>2</occurred></tag>
<tag><value>cherry</value>
<occurred>4</occurred></tag>
</result>
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Tranform" version="1.0">
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="tag">
<xsl:variable name="currentFruit"><xsl:value-of
select="."/></xsl:variable>
<xsl:variable name="others">
<xsl:number
expr="count(preceding-siblings::tag[self::*=$currentFruit]"/>
</xsl:variable>
<xsl:if test="$others=0">
<tag>
<value><xsl:value-of select="."/></value>
<occurred>
<xsl:number
expr="count(following-sibling::tag[self::*=$currentFruit]) + 1"/>
</occurred>
</tag>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
|
5. | How do I insert an element count into a document. |
| Steve Muench
For instance, I have an xml file that lists people;
I want to be able to count how many
<person></person> records are in the file and output a total.
If the current context is right, then you can simply do:
<xsl:value-of select="count(person)"/>
or you might have to augment the "person" pattern
to fit your situation...
|
6. | What does position function do |
| Ken Holman It produces the context node's position amongst the nodes of the
context node list ... which varies depending on what you are doing
(which is probably why you've had problems understanding what exactly
it is doing). In my example below, I'm explicitly setting the context
node list to the children of <list> and then relying on that in my
calculations with position().
xml file:
<list type="gloss">
<label>
<gi>front</gi>
</label>
<item>contains any prefatory matter
</item>
</list>
stylesheet:
<xsl:template match="list"> <!--document element-->
<xsl:apply-templates/> <!--set node list to children-->
</xsl:template>
<xsl:template match="item"> <!--node list = siblings-->
<xsl:variable name="this-pos"
expr="position()"/> <!--sibling #-->
Label = <xsl:value-of
select="../*[$this-pos - 1]"/> <!--prev sibling-->
</xsl:template>
output:
Label = front
From Oren Ben-Kiki
it is the position in "the context node list" of the node matched by
the pattern, not of the node matched by the <xsl:template>
containing the pattern. You can work around this by using a variable:
<xsl:variable name="position-before-me" expr="position() - 1"/>
<xsl:??? select="../*[position() = $position-before-me]"/>
But it is better to:
<xsl:??? select="preceding-sibling::*[1]"/>
See section 6.1.1 (Axes).
from: David Carlisle position() returns the position of the node currently under
consideration so things like [position()=3] gets the third thing in
the node list. [3] is short for [position()=3] so I suppose (without
checking or trying it out) that [position()] is short for
[position()=position()] which is always true.
|
7. | substring-after syntax error |
| Michael Kay
E.g.
the xslprocessor always gives me an error at the line
VALUE="{substring-after("abcd","b")"} />
What's wrong in it?
It's not well-formed. I think you mean
VALUE="{substring-after("abcd","b")}" />
David Carlisle
> Wrong: VALUE="{substring-after("abcd","b")"} />
> Right: VALUE="{substring-after("abcd","b")}" /> Wrong actually, you can't use " inside an XML attribute value
delimited by " so you have to have
"{substring-after('abcd','b')}" or '{substring-after("abcd","b")}'
'{substring-after("{
^ If you read the xsl draft it'll explain that you can not nest {
constructs, and you don't need to.
|
8. | How to show the number of nodes of the same name in a subtree |
| Linda van den Brink You can use the count function,
<xsl:number value="count(article)"/>
|
9. | How to test that a node is the last children in the xsl file |
| David Carlisle You can test if you are at the last by
<xsl:if test="position()=last()"/> but probably that isn't what you want to do, normally you would do
something like
....stuff to put before the list
<xsl:apply-templates select="animal"/>
....stuff to put after the list
ie rather than process the list one at a time asking each time if you
are at the end, it is usually more convenent to specify what you want
to put around that branch of the tree, and inbetween, select the
elements you want in that branch.
|
10. | How to format numbers and Dates |
| Ric Emery Formatting numbers can be achieved with the
format-number function of XSL.
This function is described in section 14.3 of the latest XSL spec.
Here is an example that formats a variable to 2 decimal places.
<xsl:value-of select="format-number ($SOMENUMBER, '0.00')" /> |
11. | How to isolate delimited text |
| David Carlisle
Q: Expansion.
I have a whole mess of semicolon
delimited input that looks like this:
<field>thing1; thing2;...thingn</field>
I'm trying to transform it to this:
<field>thing1</field>
<field>thing2</field>
etc...
Loops are evil. Recursion is your friend.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Tranform" version="1.0"
<xsl:template match="field">
<xsl:call-template name="split">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="split">
<xsl:param name="text" select="''"/>
<xsl:choose>
<xsl:when test="contains($text,';')">
<field>
<xsl:value-of select="substring-before($text,';')"/>
</field>
<xsl:call-template name="split">
<xsl:with-param name="text"
select="substring-after($text,';')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<field>
<xsl:value-of select="$text"/>
</field>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
|
12. | xsl: descendent count |
| David Carlisle
> How can I get CHILDREN count of an element.
e.g.
Source Tree
<CHAPTER>
<PARA>
<PARA>
<PARA>
</CHAPTER>
<xsl:template match="CHAPTER>
<xsl:value-of select="count(*)"/>
for all children, or
<xsl:value-of select="count(PARA)"/>
to tell you how many PARA children the current node has.
To use this in a test,
<xsl:if test="count(*) > 5">
true condition
</xsl:if>
|
13. | Normalise question |
| Mike Brown Q: Expansion
I modified David Carlisle's example to use normalize-space() since
whitespace distinctions are not desired. However, when I add
normalize-space(), the stylesheet stops returning
the expected "XYZ" and instead gives "XXXXYZZ"
What am I doing wrong here?
... the stylesheet ...
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Tranform" version="1.0"
xmlns:xt="http://www.jclark.com/xt"
extension-element-prefixes="xt">
<xsl:template match="root">
<xsl:for-each select="//c[not(normalize-space(text())
=normalize-space(following::c/text()))]" >
<xsl:sort order="ascending" select="." />
<xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
.... the input file ...
<?xml version="1.0"?>
<root>
<a>
<b id="101" >
<c>X</c>
</b>
<b id="102" >
<c>Y</c>
</b>
<b id="103" >
<c>X</c>
</b>
<b id="104" >
<c>Z</c>
</b>
<b id="105" >
<c>Z</c>
</b>
<b id="106" >
<c>Z</c>
</b>
</a>
<a>
<b id="201" >
<c>X</c>
</b>
<b id="202" >
<c>Z</c>
</b>
</a>
<a>
<b id="301" >
<c>X</c>
</b>
<b id="302">
<c>X</c>
</b>
</a>
</root>
Answer
I can explain why you're getting these results, but I don't have a way
to solve your problem. What you are doing wrong is trying to normalize
up to 9 text nodes at a time.
http://www.w3.org/TR/xpath#axes: "the following axis contains all
nodes in the same document as the context node that are after the
context node in document order, excluding any descendants and
excluding attribute nodes and namespace nodes."
First, why do you get 'XYZ' without the attempt at normalization?
file://c[not(text()=following::c/text())] file://c will test all the "c" element nodes in document order. Only
those for which [...] is true will be selected. The sort order you
specified will be applied to these selected nodes for purposes of
iterating through your xsl:for-each.
For each node being tested, text() is a node-set with just one member:
the 'X', 'Y', or 'Z' text node child, as expected. following::c/text()
is a node-set with every text node child of every "c" element node
from that point in the document onward, (not counting descendants of
the node being tested).
http://www.w3.org/TR/xpath#booleans: "If both objects to be compared
are node-sets, then the comparison will be true if and only if there
is a node in the first node-set and a node in the second node-set such
that the result of performing the comparison on the string-values of
the two nodes is true"
http://www.w3.org/TR/xpath#section-Text-Nodes: "The string-value of a
text node is the character data"
So then, is going through the file://c elements, is the "text()"
node-set equal to the "following::c/text()" node-set? The answer, in
the fourth column, is true (i.e., yes, they are equal) if the item in
the second column **can be found in** the third.
file://c: text(): following::c/text(): result:
<c>X</c> 'X' 'Y','X','Z','Z','Z','X','Z','X','X' true
<c>Y</c> 'Y' 'X','Z','Z','Z','X','Z','X','X' false
<c>X</c> 'X' 'Z','Z','Z','X','Z','X','X' true
<c>Z</c> 'Z' 'Z','Z','X','Z','X','X' true
<c>Z</c> 'Z' 'Z','X','Z','X','X' true
<c>Z</c> 'Z' 'X','Z','X','X' true
<c>X</c> 'X' 'Z','X','X' true
<c>Z</c> 'Z' 'X','X' false
<c>X</c> 'X' 'X' true
<c>X</c> 'X' (empty) false
Therefore, file://c[not(text()=following::c/text())] will select the
file://c items that are not true, which just happened to be these
elements:
<c>Y</c>
<c>Z</c>
<c>X</c>
...which you then sorted in ascending order and looked at the string
values of to produce 'XYZ'.
Second, why did you get 'XXXXYZZ' when you applied normalize-space() to the
node-sets in the second and third columns?
http://www.w3.org/TR/xpath#section-String-Functions: "The normalize-space
function returns the argument string with white space normalized ..."
[and] "A node-set is converted to a string by returning the
string-value of the node in the node-set that is first in document
order. If the node-set is empty, an empty string is returned."
file://c: text(): following::c/text(): result:
<c>X</c> 'X' 'Y' (and others) false
<c>Y</c> 'Y' 'X' (and others) false
<c>X</c> 'X' 'Z' (and others) false
<c>Z</c> 'Z' 'Z' (and others) true
<c>Z</c> 'Z' 'Z' (and others) true
<c>Z</c> 'Z' 'X' (and others) false
<c>X</c> 'X' 'Z' (and others) false
<c>Z</c> 'Z' 'X' (and others) false
<c>X</c> 'X' 'X' true
<c>X</c> 'X' (empty) false
Thus, file://c[not(normalize-space(text())=normalize-space(following::c/text()))]
selects:
<c>X</c>
<c>Y</c>
<c>X</c>
<c>Z</c>
<c>X</c>
<c>Z</c>
<c>X</c>
...which, when sorted and so on produces 'XXXXYZZ'.
The solution is a little beyond me, though. I'd assume that you'd have
to do it with recursive template calls that mimic the XPath evaluation
above, but with normalize-space() thrown in. It wouldn't be efficient at
all. Why don't you just normalize your source data first :)
|
14. | Substring on telephone numbers |
| Steve Muench Q: Expansion
If I have an XML document containing the following tag:
<TELEPHONE>316-855-2145</TELEPHONE> I would like to transform the above tag into the following 3 tags:
<AREACODE>316</AREACODE>
<PREFIX>855</PREFIX>
<NUMBER>2145</NUMBER>
No need for scripting to accomplish this particular
example, you can do:
<xsl:template match="TELEPHONE">
<AREACODE><xsl:value-of select="substring(.,1,3)"/></AREACODE>
<PREFIX><xsl:value-of select="substring(.,5,3)"/></PREFIX>
<NUMBER><xsl:value-of select="substring(.,9,4)"/></NUMBER>
</xsl:template>
Where the "." dot represents the current element, TELEPHONE
inside the template.
Eric van der Vlist added
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Tranform"
version="1.0">
<xsl:template match="TELEPHONE">
<AREACODE>
<xsl:value-of select="substring-before(string(), '-')"/>
</AREACODE>
<PREFIX>
<xsl:value-of
select="substring-before
substring-after(string(), '-'),'-')"/>
</PREFIX>
<NUMBER>
<xsl:value-of select=
"substring-after(substring-after(string(),'-'), '-')"/>
</NUMBER>
</xsl:template>
</xsl:stylesheet>
These functions are documented in the XPATH recommendation
(http://www.w3.org/TR/xpath).
|
15. | How to use position function |
| Natalie Rooney
I have the following xml structure:
<USERS>
<USER>bunch of attributes</USER>
<USER>bunch of attributes</USER>
<USER>bunch of attributes</USER>
</USERS>
I want to display the first user in a different way from the others.
Use (USERS/USER)[position() > 1]
|
16. | How to count the number of Choice nodes whose text is true. |
| David Carlisle <xsl:value-of select="count(Choice[normalize-space(.)='true'])"/> |
17. | Combining two tests using the and operator |
| David Carlisle How to combine
<xsl:if test ="@imagehref">
and
<xsl:if test =".[@imagehref !='']">
<xsl:if test ="@imagehref and not(@imagehref = '')" >
Michael Kay adds:
If you want to test that the current node has an imagehref attribute and
that its value is not the empty string, two simple ways to do it are:
<xsl:if test="string(@imagehref)">
<xsl:if test="@imagehref!=''">
You don't actually need to use the and operator.
|
18. | normlise-space function |
| David Carlisle "normalize-space(list[listitem='Related topics'])" would select the list nodes with listitemm child equal to
(exactly) 'Related topics' Then take the string value of
that list, and normalise it's white space. As it's used in
a boolean context, this swill be true if its non empty.
Probably what you want to do is select the list elements
with listitem elements with normalised value 'Related
topics'
which is
"list[normalize-space(listitem)='Related topics']"
|
19. | How to implement a for loop in XSLT |
| Michael Kay
> In XSL, how can I get something like (not from XML)
>
> for i=1 to 4
> <td>i</td>
> next
<xsl:call-template name="repeat-td">
<xsl:with-param name="n" select="4"/>
</xsl:call-template>
<xsl:template name="repeat-td">
<xsl:param name="n"/>
<xsl:if test="$n != 0">
<td>i</td>
<xsl:call-template name="repeat-td">
<xsl:with-param name="n" select="$n - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
|
20. | Logical and function in XSL Transformations |
| Mike Kay
I want to know whether operations like 'AND' can be performed in XSL
transformations. Example:
<xsl:template match="order & paid">
<DONE/>
</xsl:template>
<xsl:template match="*[order and paid]"> will match any element that has at
least one order child and at least one paid child, if that's what you mean.
|
21. | How to use the not function |
| Juliane Harbarth
>How do I say <xsl:apply-templates select="[all but element3]" /> <xsl:apply-templates select="*[name() != 'element3']" /> or Mike Kay adds:
<xsl:apply-templates
select="*[not(self::element3)]"/>
This selects all the child
elements that don't satisfy the boolean condition
self::element3, i.e. all those for which self::element3 is
an empty node-set, i.e. all those which are not element3
elements.
Generally speaking, it's best to avoid testing the result of
the name() function, because two XML documents that are
equivalent except for their choice of namespace prefixes
will give different answers. Using "not(self::element3)" is
safer.
|
22. | Character Translation |
| David Carlisle
Q expansion.
> Could I use translate to translate new lines code to \n? No translate can only translate characters to characters,
however you can use a named function to replace the
substring by the substring \n, eg this one:
<!-- replace all occurences of the character(s) `from'
by the string `to' in the string `string'.-->
<xsl:template name="string-replace" >
<xsl:param name="string"/>
<xsl:param name="from"/>
<xsl:param name="to"/>
<xsl:choose>
<xsl:when test="contains($string,$from)">
<xsl:value-of select="substring-before($string,$from)"/>
<xsl:value-of select="$to"/>
<xsl:call-template name="string-replace">
<xsl:with-param name="string"
select="substring-after($string,$from)"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
|
23. | How to get the position of an element with descendant y with value z |
| Kay Michael
> I would want to get the position of the element
> that has a descendant 'y' whose value is 'z' and
> put it in a variable, in the example the value of
> the variable should be 2.
<?xml version="1.0"?>
<elementList>
<element>
<x>
<y>a</y>
</x>
</element>
<element>
<x>
<y>z</y>
</x>
</element>
</elementList> Try something like:
<xsl:variable name="pos">
<xsl:for-each select="//element[descendant::y[.='z']][1]">
<xsl:number/>
<xsl:for-each>
</xsl:variable>
or (perhaps more efficient)
<xsl:variable name="pos">
<xsl:for-each select="//element">
<xsl:if test="*[descendant::y[.='z']]">
<xsl:value-of select="position()"/>
</xsl:if>
<xsl:for-each>
</xsl:variable>
This won't work if there is more than one element that
satisfies the condition.
|
24. | Sum function |
| Steve Muench
Q expansion
I'm trying to sum report totals, but I only want to sum
certain attributes. For example
My.xml
<report>
<tranxs>
<tranx cccf="N" amt="200">
</tranx>
</report>
My.xsl
<xsl:for-each select="report">
...
<xsl:value-of select="sum(tranxs/tranx[@cccf = 'N']/@amt)"/>
I know the above line is wrong, but I don't know how to fix
it. I want it to sum depending on the cccf attribute value.
Given:
<report>
<tranxs>
<tranx cccf="N" amt="1"/>
<tranx cccf="Y" amt="2"/>
<tranx cccf="N" amt="3"/>
<tranx cccf="Y" amt="4"/>
</tranxs>
</report>
and:
<foo xsl:version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:for-each select="report">
<xsl:value-of
select="sum(tranxs/tranx[@cccf = 'N']/@amt)"/>
</xsl:for-each>
</foo>
I get:
<foo>4</foo>
which is the sum of 1 and 3.
|
25. | position function |
| Mike Brown, David Carlisle, Ken Holman.
Problem: position is returning a different number
depndant on where its called from.
I'm using it in the root template in a for-each loop and in a template.
The 'number' I'm getting back is different in each case.
If you trace the order of execution of templates, starting at the
root node, and taking into account the built-in templates, you will
probably find that you told the XSL processor to process the
elements as part of a list of nodes in which those element nodes
occupy the 2nd, 6th, 10th, etc positions in the list, rather than
the 1 to 7th position expected. David C adds > I'm using position() function in two places. Or more particularly, in two current node lists, position()
gives the position of the current node in the current node list
so you get two different answers. <xsl:for-each select="faq/body/section" > so here you have constructed a list of all sections that are children of
body elements which are children of faq elements of the current node.
So position() returns the position in that list.
<xsl:template match="section" >
<xsl:variable name="secno"><xsl:value-of
select="position()"/></xsl:variable>
Here you didn't show what the node list was. for instance if you go <xsl:apply-templates select="section[3]"/> then
you'll construct a node list of length one consisting of the third
section child of the current element, so the above template will
show a value of position() as 1. If you go
<xsl:apply-templates /> in a template matching on the parent of your
section element then the above template will fire and this time
position() will be the position in the list selected by default,
which includes all elements text, comments, etc. So you don't need to give a template and a for-each example, the same
template will give different values for position, depending on the list
to which it is applied. Ken Holman adds Perhaps you are in <body> and you are asking that all child nodes be pushed
through your stylesheet with <xsl:apply-templates/> (with no select=
attribute) ... you are going to get *all* child nodes of <body> pushed, not
just <section> nodes.
<body>
<title/>
<section/>
<section/>
<note/>
<section/>
</body>
The sections above would be counted as 4, 6, and 10 due to the intervening
element nodes and the intervening text nodes. It is often the text nodes
that I forget about. I've illustrated this with a diagram on page 58 of
the 7th edition. The SHOWTREE stylesheet in the free resources section of our web site
exposes a report of all nodes in the instance ... run your input through
that stylesheet to see all of the intervening nodes.
|
26. | Using concat
>The XPath spec uses concat(expression1,expression2,expression3*) as the
>example...
>The SAXON documentation shows concat(expression1{,expression2}*)...
>I can't get either to work.
|
| Christopher R. Maden
Both of those syntactic expressions are somewhat misleading. The XPath one
means that concat() takes multiple expressions, separated by commas. The
asterisk indicates that there should be two, and that the third one is not
only optional but also repeatable (i.e., that there may be more than three
arguments). The SAXON documentation correctly observes that concat() can take one
expression. The curly braces encapsulate both the comma and the second
expression, and indicate that the encapsulation may be repeated.
[Mike Kay notes: (July 2000, Saxon at 5.3.2) The Saxon code actually allows concat() to have zero or more arguments. This
is a non-conformance (sigh), the spec says an error should be reported if
there are less than two.] Both are saying that concat() takes multiple arguments separated by commas.
The main problem you're having is with the nature of an expression. The
variable part ($sect1num) is correct, since a variable reference is a valid
expression. But the .htm for instance is wrong. A literal string
expression must be surrounded by quotes: '.htm'. It's not clear whether
sect1 is an XPath or a string. If it were a string, you'd want
concat('sect1', $sect1num, '.htm') If sect1 is a path intended to get the value of the sect1 child of the
current node, then you would do
concat(sect1, $sect1num, '.htm') |