Context node:
<a>
<c refid="1" />
<c refid="2" />
<c refid="3" />
<c refid="4" />
<c refid="5" />
</a>
It gets the nodes referred to above, using a proprietary command:
<xsl:for-each select="get-a(#refid)">
<a id="1">
<f att1="C"/>
<f att2="I"/>
</a>
<a id="2">
<f att1="C"/>
<f att2="I"/>
</a>
<a id="3">
<!--doesn't have f att1-->
<f att2="I"/>
</a>
<a id="4">
<f att1="R"/>
<f att2="S"/>
</a>
<a id="5">
<f att1="G"/>
<f att2="I"/>
</a>
At present, I have it call a template within a for-each, but that will only do each node separately, obviously.
But it must process them first based on the att2 value (these are set values, always I or S, so no problem), and then within that, based on att1 value to produce something like below, the first P node being the problem:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0">
<!--xsl:output method="xml" indent="no" encoding="UTF-8"/-->
<!-- This takes the first asset (betting that it's correct...) and uses it to establish the parameters below -->
<xsl:variable name="rootSourceAssetXml">
<xsl:copy-of select="/asset[1]"/>
<!--xsl:copy-of select="./asset"/-->
</xsl:variable>
<xsl:template match="/">
<xsl:variable name="xml">
<O>
<xsl:for-each select="$rootSourceAssetXml/asset/child_asset_rel[#key='user.']">
<xsl:sort select="cs:get-asset(#child_asset)/f/#att2" order="ascending" data-type="text"/>
<xsl:sort select="cs:get-asset(#child_asset)/f/#att1" order="descending" data-type="text"/>
<xsl:variable name="getChapter">
<xsl:variable name="getChapterXML" select="cs:get-asset(#child_asset)"/>
<xsl:for-each select="$getChapterXML">
<xsl:if test="$getChapterXML/f/#att2='ebook'">
<xsl:copy-of select="$getChapterXML"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:call-template name="Group">
<xsl:with-param name="thingGroup" select="$getChapter"/>
<xsl:with-param name="resourceTypeWeb" select="'EBook'"/>
</xsl:call-template>
<xsl:variable name="getChapter">
<xsl:variable name="getChapterXML" select="cs:get-asset(#child_asset)"/>
<xsl:for-each select="$getChapterXML">
<xsl:if test="$getChapterXML/f att2='instructor'">
<xsl:copy-of select="$getChapterXML"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:call-template name="Group">
<xsl:with-param name="thingGroup" select="$getChapter"/>
<xsl:with-param name="resourceTypeWeb" select="'Instructor'"/>
</xsl:call-template>
</xsl:for-each>
</O>
</xsl:variable> <!-- Closes xml variable block -->
</xsl:template>
<xsl:template name="Group">
<xsl:param name="thingGroup"/>
<xsl:param name="resourceTypeWeb"/>
<xsl:variable name="eachAsset">
<xsl:for-each select="$thingGroup">
<xsl:copy-of select="$thingGroup/asset"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="productResourceDescriptionGroupWeb" select="$eachAsset/asset/asset_feature[#feature='XXX:product-resource-description-group-web']/#value_string"/>
<xsl:variable name="productResourceDescriptionDetailWeb" select="$eachAsset/asset/asset_feature[#feature='XXX:product-resource-description-detail-web']/#value_string"/>
<xsl:for-each select="$eachAsset/asset">
<P>
<xsl:attribute name="e"><xsl:value-of select="$resourceTypeWeb"/></xsl:attribute>
<xsl:attribute name="d"><xsl:value-of select="$productResourceDescriptionGroupWeb"/></xsl:attribute>
<xsl:call-template name="Detail">
<xsl:with-param name="resourceTypeWeb" select="$resourceTypeWeb"/>
<xsl:with-param name="topAssetTypeName" select="$topAssetTypeName"/>
<xsl:with-param name="thing" select="$eachAsset"/>
<xsl:with-param name="productResourceDescriptionDetailWeb" select="$productResourceDescriptionDetailWeb"/>
</xsl:call-template>
</P>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Should produce this:
<O>
<P d="C" e="I"> <!-- This C is not a set value but a string, and it could be anything. Once the nodes with the same attribute are isolated, it is fine to grab the att value from node in position 1-->
<id1>
<!-- other info via call template; this works -->
</id1>
<id2>
<!-- other info via call template; this works -->
</id2>
</P>
<P d="NULL" e="I">
<id3>
<!-- other info via call template; this works -->
</id3>
</P>
<P d="G" e="I">
<id5>
<!-- other info via call template; this works -->
</id5>
</P>
<P d="R" e="S">
<id4>
<!-- other info via call template; this works -->
</id4>
</P>
</O>
I have tried for-each-group calling a different template, and for-each with a sort for the value of att1, and other methods with no success.
This gives the right order, but I cannot bring nodes with same C value together:
<xsl:sort select="a/f/#att2"/>
<xsl:sort select="a/f/#att1"/>
The logic should be
for each <a> with same att2 value
for each <a> with same att1 value
output a single P with d=att1 value
then process nodes with same att1
I know XSLT can't "loop" the way I'm used to with Perl, but I feel there is some way to do this by grouping or sorting, I just can't find the right combination. I keep getting so close, but then can't complete it.
Many thanks in advance.
Your question is (still) very confusing. Consider the following simplified example:
Input XML
<root>
<a id="1">
<f att1="C"/>
<f att2="I"/>
</a>
<a id="2">
<f att1="C"/>
<f att2="I"/>
</a>
<a id="3">
<!--doesn't have f att1-->
<f att2="I"/>
</a>
<a id="4">
<f att1="R"/>
<f att2="S"/>
</a>
<a id="5">
<f att1="G"/>
<f att2="I"/>
</a>
</root>
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="root">
<root>
<xsl:for-each-group select="a" group-by="f/#att2">
<xsl:for-each-group select="current-group()" group-by="if (f/#att1) then f/#att1 else 'NULL'">
<P d="{current-grouping-key()}" e="{f/#att2}">
<xsl:for-each select="current-group()">
<abc id="{#id}"/>
</xsl:for-each>
</P>
</xsl:for-each-group>
</xsl:for-each-group>
</root>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<P d="C" e="I">
<abc id="1"/>
<abc id="2"/>
</P>
<P d="NULL" e="I">
<abc id="3"/>
</P>
<P d="G" e="I">
<abc id="5"/>
</P>
<P d="R" e="S">
<abc id="4"/>
</P>
</root>
This shows how to implement your stated logic:
for each <a> with same att2 value
for each <a> with same att1 value
output a single P with d=att1 value
then process nodes with same att1
and nothing else.
Related
I need to format some xml data with the following structure
<list>
<item>
Test
</item>
<item>
Testt
</item>
<or-item>
TestOr
</or-item>
<or-item>
TestOrr
</or-item>
<item>
Testtt
</item>
<or-item>
TestOrrr
</or-item>
<item>
Testttt
</item>
</list>
with xsl:number the or-item must be formatted with the second level count on that position. I know it would be better to structure the or-item inside that item but the data is given like that.
I need a way to count the or-item next to the current or-item to calculate the numbering for xsl:number
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.1"
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output encoding="UTF-8" method="html" indent="yes"/>
<xsl:template match="list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number count="item"/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number value="count(//or-item)" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
Edit
I am using XSLT 1.1 with xsltproc on linux but 2.0 whould be possible if neccessary
As the target format is HTML, it seems you could rely on creating the appropriate nested HTML ordered lists by using xsl:for-each-group and group-starting-with="item":
<xsl:template match="list">
<ol>
<xsl:for-each-group select="*" group-starting-with="item">
<li>
<xsl:value-of select="."/>
<xsl:where-populated>
<ol>
<xsl:apply-templates select="tail(current-group())"/>
</ol>
</xsl:where-populated>
</li>
</xsl:for-each-group>
</ol>
</xsl:template>
<xsl:template match="or-item">
<li>
<xsl:value-of select="."/>
</li>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM
That example uses some XSLT/XPath 3 stuff like were-populated and tail but in case that XSLT 2 compatility is needed then it could be replaced by <xsl:if test="subsequence(current-group(), 2)"><ol><xsl:apply-templates select="subsequence(current-group(), 2)"/></xsl:if>.
And of course the use of HTML ordered lists is not necessary, if needed/wanted you could just transform the input to nested divs with the used grouping approach and then in a second step use format-number as you seem to want to do:
<xsl:template match="list">
<xsl:variable name="nested-list">
<xsl:for-each-group select="*" group-starting-with="item">
<xsl:copy>
<xsl:value-of select="."/>
<xsl:copy-of select="tail(current-group())"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:variable>
<div>
<xsl:apply-templates select="$nested-list"/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM/1
You can produce the expected output by simply adjusting the xsl:number instruction:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number level="any" from="item" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
This is a variant on the common request for an XPath to return all siblings until some condition, answered with characteristic fullness by Dimitre Novatchev at XPath axis, get all following nodes until using this pattern:
$x/following-sibling::p
[1 = count(preceding-sibling::node()[name() = name($x)][1] | $x)]
But that pattern relies on the symmetry of following-sibling and preceding-sibling, on the ability to look in both directions along an axis.
Is there a comparable pattern when the axis is ancestor-or-self?
For example:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
The straighforward
<xsl:template match="img">
<xsl:for-each select="ancestor-or-self::*[#xml:base]">
<xsl:value-of select="#xml:base"/>
</xsl:for-each>
<xsl:value-of select="#url"/>
</xsl:template>
would return
/news/reports/sports/photos/A1.jpg
/news/reports/sports/photos/A1.jpg
but if
<c xml:base="sports/" >
were instead
<c xml:base="/sports/" >
with that leading /, the for-each needs to stop, so as to return
/sports/photos/A1.jpg
/sports/photos/A2.jpg
How (in XSLT/XPath 1.0) to make it stop?
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pWanted" select="//img"/>
<xsl:param name="pWantedAttr" select="'url'"/>
<xsl:template match="/">
<xsl:apply-templates select="$pWanted"/>
</xsl:template>
<xsl:template match="*[not(starts-with(#xml:base, '/'))]">
<xsl:apply-templates select="ancestor::*[#xml:base][1]"/>
<xsl:value-of select="concat(#xml:base,#*[name()=$pWantedAttr])"/>
<xsl:if test="not(#xml:base)"><xsl:text>
</xsl:text></xsl:if>
</xsl:template>
<xsl:template match="*[starts-with(#xml:base, '/')]">
<xsl:value-of select="#xml:base"/>
</xsl:template>
</xsl:stylesheet>
when applied to this XML document:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="/sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
produces the wanted, correct result:
/sports/photos/A1.jpg
/sports/photos/A2.jpg
Update -- A single XPath 2.0 expression solution:
for $target in //img,
$top in $target/ancestor::*[starts-with(#xml:base,'/')][1]
return
string-join(
(
$top/#xml:base
, $top/descendant::*
[#xml:base and . intersect $target/ancestor::*]
/#xml:base
, $target/#url,
'
'
),
''
)
XSLT 2.0 - based verification:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:sequence select=
"for $target in //img,
$top in $target/ancestor::*[starts-with(#xml:base,'/')][1]
return
string-join(
(
$top/#xml:base
, $top/descendant::*
[#xml:base and . intersect $target/ancestor::*]
/#xml:base
, $target/#url,
'
'
),
''
)
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
the XPath expression is evaluated and the result from this evaluation is copied to the output:
/news/reports/sports/photos/A1.jpg
/news/reports/sports/photos/A2.jpg
With the modified document:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="/sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
again the wanted, correct result is produced:
/sports/photos/A1.jpg
/sports/photos/A2.jpg
Update2:
The OP has suggested this simplification:
Update added by original poster: Once embedded in the full
application, where the full url replaced the relative one, Dimitre's
approach ended up being this simple
:
<xsl:template match="#url">
<xsl:attribute name="url">
<xsl:apply-templates mode="uri" select=".." />
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="*" mode="uri">
<xsl:if test="not(starts-with(#xml:base, '/'))">
<xsl:apply-templates select="ancestor::*[#xml:base][1]" mode="uri"/>
</xsl:if>
<xsl:value-of select="#xml:base"/>
</xsl:template>
There is a way to select the right nodes in a single for-each select expression:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="img">
<xsl:for-each select="
ancestor-or-self::*[
starts-with(#xml:base, '/')
][1]/descendant-or-self::*[
#xml:base and .//img[generate-id() = generate-id(current())]
]">
<xsl:value-of select="#xml:base"/>
</xsl:for-each>
<xsl:value-of select="#url"/>
</xsl:template>
</xsl:stylesheet>
Given this input XML:
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="/sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
The correct result is produced:
/sports/photos/A1.jpg
/sports/photos/A2.jpg
The XPath expression could be read as "Beginning with the closest ancestor whose #xml:base starts with a slash, select that and all of its descendants who have the current <img> as one of their descendants."
This effectively selects exactly the one correct path down into the XML tree.
I found it: Count the ancestors that meet the condition before going into the for-each loop. Test each candidate to see if the count of its condition-matching ancestors is the same.
This stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="img">
<xsl:variable name="distance" select="count(ancestor-or-self::*[#xml:base][substring(#xml:base,1,1)='/'])" />
<xsl:for-each select="ancestor-or-self::*[#xml:base][
count(ancestor-or-self::*[#xml:base][substring(#xml:base,1,1)='/'])=$distance
]">
<xsl:value-of select="#xml:base"/>
</xsl:for-each>
<xsl:value-of select="#url"/>
</xsl:template>
</xsl:stylesheet>
applied to the given XML
<t>
<a xml:base="/news/" >
<b xml:base="reports/">
<c xml:base="politics/" />
<c xml:base="/sports/" >
<d xml:base="reports/" />
<d xml:base="photos/" >
<img url="A1.jpg" />
<img url="A2.jpg" />
</d>
</c>
<c xml:base="entertainment" />
</b>
</a>
</t>
returns the desired result:
/sports/photos/A1.jpg
/sports/photos/A2.jpg
Without the / before sports, the for-each matches ancestors all the way to /news:
/news/reports/sports/photos/A1.jpg
/news/reports/sports/photos/A2.jpg
With a minor change to your original template:
<xsl:template match="img">
<xsl:for-each select="ancestor-or-self::*[#xml:base][not(.//*[starts-with(#xml:base, '/')])]">
<xsl:value-of select="#xml:base"/>
</xsl:for-each>
<xsl:value-of select="#url"/>
</xsl:template>
So I have an xml file containing
...
<chapter>
<para>This line has a quote <quote id="one"/>. Here is some more text.</para>
<para>This also has a quote <quote id="two"/>. Here is some more text.</para>
</chapter>
<references>
<source id="one">
<author>Author 1</author>
<title>Title 1</title>
<year>2001</year>
</source>
<source id="two">
<author>Author 2</author>
<title>Title 2</title>
<year>2002</year>
</source>
</references>
...
I would like to output an xhtml
...
<p>This line has a quote <a href="#one>[1]</a>. Here is some more text.</p>
<p>This also has a quote <a href="#two>[2]</a>. Here is some more text.</p>
<h3>References</h3>
<ol>
<li><a name="one">Author 1, Title 1, 2001</a></li>
<li><a name="two">Author 2, Title 2, 2002</a></li>
</ol>
...
So what I want is a quote inside text with a link to an item in the references list.
I would also like for references to be ordered as they appear in text.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="quote">
<a href="#{#id}">
<xsl:text>[</xsl:text>
<xsl:number count="quote" level="any" />
<xsl:text>]</xsl:text>
</a>
</xsl:template>
<xsl:template match="references">
<h3>References</h3>
<ol>
<xsl:apply-templates/>
</ol>
</xsl:template>
<xsl:template match="source">
<li>
<a name="{#id}">
<xsl:apply-templates/>
</a>
</li>
</xsl:template>
<xsl:template match="author|title">
<xsl:value-of select="."/>
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template match="year">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
I have the following XML
<data>
<records>
<record name="A record">
<info>A1</info>
<info>A2</info>
</record>
<record name="B record"/>
<record name="C record">
<info>C1</info>
</record>
</records>
</data>
how can I transform into following output, the problem is how can I count between on record, and record/info?
<div id="1">
<p>A record</p>
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<p>C record</p>
<span id="3">C1</span>
</div>
Solution 1. Fine grained traversal. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="records">
<xsl:apply-templates select="*[1]"/>
</xsl:template>
<xsl:template match="record"/>
<xsl:template match="record[node()]">
<xsl:param name="pRecordNum" select="1"/>
<xsl:param name="pInfoNum" select="1"/>
<div id="{$pRecordNum}">
<xsl:apply-templates select="#*|*[1]">
<xsl:with-param name="pInfoNum" select="$pInfoNum"/>
</xsl:apply-templates>
</div>
<xsl:apply-templates select="following-sibling::record[node()][1]">
<xsl:with-param name="pRecordNum" select="$pRecordNum +1"/>
<xsl:with-param name="pInfoNum" select="$pInfoNum + count(info)"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="info">
<xsl:param name="pInfoNum"/>
<span id="{$pInfoNum}">
<xsl:value-of select="."/>
</span>
<xsl:apply-templates select="following-sibling::info[1]">
<xsl:with-param name="pInfoNum" select="$pInfoNum +1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Output:
<div id="1">
<p>A record</p>
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<p>C record</p>
<span id="3">C1</span>
</div>
Solution 2: preceding axe. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="record"/>
<xsl:template match="record[node()]">
<div id="{count(preceding-sibling::record[node()])+1}">
<xsl:apply-templates select="#*|*"/>
</div>
</xsl:template>
<xsl:template match="info">
<span id="{count(preceding::info)+1}">
<xsl:value-of select="."/>
</span>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Solution 3: With fn:position() and preceding axe. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="records">
<xsl:apply-templates select="record[node()]"/>
</xsl:template>
<xsl:template match="record">
<div id="{position()}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="info">
<span id="{count(preceding::info)+1}">
<xsl:value-of select="."/>
</span>
</xsl:template>
<xsl:template match="#name">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>
Note: You need a explict pull style.
Edit: Missed any level numbering for span/#id.
There is a short way to do this in XSLT. Use the <xsl:number> instruction:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record[info]">
<xsl:variable name="vPos">
<xsl:number count="record[info]"/>
</xsl:variable>
<div id="{$vPos}">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="info">
<xsl:variable name="vPos">
<xsl:number from="/" level="any" count="info" />
</xsl:variable>
<span id="{$vPos}"><xsl:apply-templates/></span>
</xsl:template>
<xsl:template match="record[not(info)]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<data>
<records>
<record name="A record">
<info>A1</info>
<info>A2</info>
</record>
<record name="B record"/>
<record name="C record">
<info>C1</info>
</record>
</records>
</data>
the wanted, correct result is produced:
<data>
<records>
<div id="1">
<span id="1">A1</span>
<span id="2">A2</span>
</div>
<div id="2">
<span id="3">C1</span>
</div>
</records>
</data>
I need to check if a param has got a value in it and if it has then do this line otherwise do this line.
I've got it working whereas I don't get errors but it's not taking the right branch
The branch that is wrong is in the volunteer_role template
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="volunteers-by-region" match="volunteer" use="region" />
<xsl:template name="hoo" match="/">
<html>
<head>
<title>Registered Volunteers</title>
<link rel="stylesheet" type="text/css" href="volunteer.css" />
</head>
<body>
<h1>Registered Volunteers</h1>
<h3>Ordered by the username ascending</h3>
<xsl:for-each select="folktask/member[user/account/userlevel='2']">
<xsl:for-each select="volunteer[count(. | key('volunteers-by-region', region)[1]) = 1]">
<xsl:sort select="region" />
<xsl:for-each select="key('volunteers-by-region', region)">
<xsl:sort select="folktask/member/user/personal/name" />
<div class="userdiv">
<xsl:call-template name="volunteer_volid">
<xsl:with-param name="volid" select="../volunteer/#id" />
</xsl:call-template>
<xsl:call-template name="volunteer_role">
<xsl:with-param name="volrole" select="../volunteer/roles" />
</xsl:call-template>
<xsl:call-template name="volunteer_region">
<xsl:with-param name="volloc" select="../volunteer/region" />
</xsl:call-template>
</div>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
<xsl:if test="position()=last()">
<div class="count">
<h2>Total number of volunteers: <xsl:value-of select="count(/folktask/member/user/account/userlevel[text()=2])" />
</h2>
</div>
</xsl:if>
</body>
</html>
</xsl:template>
<xsl:template name="volunteer_volid">
<xsl:param name="volid" select="'Not Available'" />
<div class="heading2 bold"><h2>VOLUNTEER ID: <xsl:value-of select="$volid" /></h2></div>
</xsl:template>
<xsl:template name="volunteer_role">
<xsl:param name="volrole" select="'Not Available'" />
<div class="small bold">ROLES:</div>
<div class="large">
<xsl:choose>
<xsl:when test="string-length($volrole)!=0">
<xsl:value-of select="$volrole" />
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:template>
<xsl:template name="volunteer_region">
<xsl:param name="volloc" select="'Not Available'" />
<div class="small bold">REGION:</div>
<div class="large"><xsl:value-of select="$volloc" /></div>
</xsl:template>
</xsl:stylesheet>
And the XML:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="volunteers.xsl"?>
<folktask xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:noNamespaceSchemaLocation="folktask.xsd">
<member>
<user id="1">
<personal>
<name>Abbie Hunt</name>
<sex>Female</sex>
<address1>108 Access Road</address1>
<address2></address2>
<city>Wells</city>
<county>Somerset</county>
<postcode>BA5 8GH</postcode>
<telephone>01528927616</telephone>
<mobile>07085252492</mobile>
<email>adrock#gmail.com</email>
</personal>
<account>
<username>AdRock</username>
<password>269eb625e2f0cf6fae9a29434c12a89f</password>
<userlevel>4</userlevel>
<signupdate>2010-03-26T09:23:50</signupdate>
</account>
</user>
<volunteer id="1">
<roles></roles>
<region>South West</region>
</volunteer>
</member>
<member>
<user id="2">
<personal>
<name>Aidan Harris</name>
<sex>Male</sex>
<address1>103 Aiken Street</address1>
<address2></address2>
<city>Chichester</city>
<county>Sussex</county>
<postcode>PO19 4DS</postcode>
<telephone>01905149894</telephone>
<mobile>07784467941</mobile>
<email>ambientexpert#yahoo.co.uk</email>
</personal>
<account>
<username>AmbientExpert</username>
<password>8e64214160e9dd14ae2a6d9f700004a6</password>
<userlevel>2</userlevel>
<signupdate>2010-03-26T09:23:50</signupdate>
</account>
</user>
<volunteer id="2">
<roles>Van Driver,gas Fitter</roles>
<region>South Central</region>
</volunteer>
</member>
</folktask>
The following should do the trick:
<xsl:template name="volunteer_volid">
<xsl:param name="volid" />
<xsl:choose>
<xsl:when test="string-length($volid) > 0">
<div class="heading2 bold"><h2>VOLUNTEER ID: <xsl:value-of select="$volid" /></h2></div>
</xsl:when>
<xsl:otherwise>
<!-- No volid -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I've replaced the default value with an empty string so that not providing a parameter value is the same as providing the parameter value as "". If this isn't the desired behaviour then change the parameters select and modify the test expression accordingly, for example:
$volid != 'Not Available'