Sort sequence from distinct values - xpath-2.0

What is the cleanest way to sort a sequence I get from distinct-values()? I use XPath 2.0 and my files are the following:
This is my XML file
<root>
<value>3</value>
<value>1</value>
<value>7</value>
</root>
and this is my transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="root">
<output>
<xsl:variable name="availableValues" select="distinct-values(value)"/>
<!-- now the availableValues contains the sequence (3,1,7) -->
<!-- My goal is to get this into a sorted sequence (1,3,7) -->
<!-- This is just for testing the $availableValues -->
<xsl:attribute name="count" select="count($availableValues)"/>
<xsl:for-each select="$availableValues">
<val>
<xsl:value-of select="."/>
</val>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
I have tried to use
<xsl:variable name="availableValues">
<xsl:for-each select="distinct-values(value)">
<xsl:sort/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
but this (of course) doesn't give me a sequence, just the concatenation of the values (although sorted).
EDIT:
This is what I came up so far, and it looks like a proper solution. Is there a better solution?
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
<xsl:output indent="yes"/>
<xsl:template match="root">
<output>
<xsl:variable name="availableValues" as="xs:integer *">
<xsl:for-each select="distinct-values(value)">
<xsl:sort/>
<xsl:sequence select="."/>
</xsl:for-each>
</xsl:variable>
<!-- This is just for testing the $availableValues -->
<xsl:attribute name="count" select="count($availableValues)"/>
<xsl:for-each select="$availableValues">
<val>
<xsl:value-of select="."/>
</val>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>

Use xsl:perform-sort.
Here is an example from the link:
<xsl:perform-sort select="//BOOK">
<xsl:sort select="author/last-name"/>
<xsl:sort select="author/first-name"/>
</xsl:perform-sort>

Related

Remove Trailing zeros using xsl1.0 or xslt 2.0

I have to sum the values from xml and remove the Trailing zeros from xml.
can you help me to remove this using xsl1.0 or xslt2.0
I have tried with number(.) but its not removing the trailing zeros.
My input is below
<test>
<loop>
<lines>
<linesTotal>2010</linesTotal>
</lines>
<lines>
<linesTotal>20</linesTotal>
</lines>
</loop>
</test>
Expected output is
203
but it results 2030
Please help me!
I can't see why this would be useful (at least not in the given example), and I strongly suspect you don't really want to do this.
But - mainly for fun - here's a method to remove any trailing zeros from the result:
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:template match="/test">
<output>
<xsl:call-template name="remove-trailing-zeros">
<xsl:with-param name="number" select="sum(loop/lines/linesTotal)"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="remove-trailing-zeros">
<xsl:param name="number"/>
<xsl:choose>
<xsl:when test="$number and not($number mod 10)">
<xsl:call-template name="remove-trailing-zeros">
<xsl:with-param name="number" select="$number div 10"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$number"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0, you could make this a function instead of a template. Or move the problem to the string domain (as suggested in another answer):
<xsl:template match="/test">
<xsl:param name="sum" select="sum(loop/lines/linesTotal)"/>
<output>
<xsl:value-of select="replace(string($sum), '0+$', '')"/>
</output>
</xsl:template>
You can use replace function using 2.0:
replace(string(2010+30), '0$', '')

Create a CSV from a SQL query using XSL

I need help to create a CSV file based on a SQL query.
This is how the current XSL looks like:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:param name="fieldNames" select="'yes'" />
<xsl:template match="NewDataSet">
<xsl:text></xsl:text>
<xsl:apply-templates select="Table"/>
</xsl:template>
<xsl:template match="Table">
<xsl:for-each select="*">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Everything works fine, except the fact that I need to add a header row in the CSV with predefined values for each value. How can I do that?
This is how it looks now: enter image description here
This is how I want the CSV to look like: enter image description here
Add e.g.
<xsl:template match="/">
<xsl:text>foo,bar,baz
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
where foo,bar,baz would be your column names.

Concat using for each loop and and keyword

Recently I've encountered a case where in i should apply a for each loop and concat strings using 'and' keyword. Below is a part of my xml document.
<?xml version="1.0" encoding="utf-8"?>
<case.ref.no.group>
<case.ref.no>
<prefix>Civil Appeal</prefix>
<number>W-02-887</number>
<year>2008</year>
</case.ref.no>
<case.ref.no>
<prefix>Civil Appeal</prefix>
<number>W-02-888</number>
<year>2008</year>
</case.ref.no>
</case.ref.no.group>
and i tried the below xslt on it.
<xsl:template match="case.ref.no.group">
<xsl:variable name="pre">
<section class="sect2">
<xsl:text disable-output-escaping="yes">Court of Appeal</xsl:text>
</section>
</xsl:variable>
<xsl:variable name="tex">
<xsl:value-of select="./case.ref.no/prefix"/>
</xsl:variable>
<xsl:variable name="iter">
<xsl:value-of select="./case.ref.no/number"/>
<xsl:if test="following::case.ref.no/number">;</xsl:if>
</xsl:variable>
<xsl:variable name="year">
<xsl:value-of select="./case.ref.no/year"/>
</xsl:variable>
<div class="para">
<xsl:value-of select="concat($pre,' – ',$tex,' Nos. ',$iter,'-',$year)"/>
</div>
</xsl:template>
when i try to run it it is giving me the below output.
Court of Appeal – Civil Appeal Nos. W-02-887 2008
but i want it to be as below.
Court of Appeal – Civil Appeal Nos. W-02-887-2008 and W-02-888-2008
please let me know how i can achieve this. i'm doing this in xslt 1.0.
Thanks
I don't understand well what exactly you are trying to do. You mentioned for-each but in your code is not present, you mentioned word and and you don't use it :-)
If I use following stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<output>
<xsl:apply-templates select="case.ref.no.group" />
</output>
</xsl:template>
<xsl:template match="case.ref.no.group">
<section class="sect2">
<xsl:text>Court of Appeal</xsl:text>
</section>
<xsl:text> - </xsl:text>
<xsl:value-of select="case.ref.no[1]/prefix" />
<xsl:text> Nos. </xsl:text>
<xsl:for-each select="case.ref.no">
<xsl:value-of select="number" />
<xsl:text>-</xsl:text>
<xsl:value-of select="year" />
<xsl:if test="not(position() = last())">
<xsl:text> and </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I get this result
<?xml version="1.0" encoding="UTF-8"?>
<output xmlns:fo="http://www.w3.org/1999/XSL/Format"><section class="sect2">Court of Appeal</section> - Civil Appeal Nos. W-02-887-2008 and W-02-888-2008</output>
But as I said I'm not sure if I understand your needs well. For example I'm not sure if you don't need some kind of grouping (prefix will be everytime same in all <case.ref.no> under one parent <case.ref.no.group>?) etc.

Adding number in a CSV in XSLT

How can you add numbers in a CSV in XSLT 1 ?
I want to take:
<num>1,2,3</num>
and get the sum of the numbers in the element, so we would get 6 from the above.
Using FXSL and the str-split-to-words template (I am lazy to write recursive templates which is time consuming and error-prone :) ):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="vwordNodes">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="."/>
<xsl:with-param name="pDelimiters" select="','"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vNums" select="ext:node-set($vwordNodes)/*"/>
<xsl:value-of select="sum($vNums)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<num>1,2,3</num>
the wanted, correct result is produced:
6

Recursive transformations using xslt, xpath:document() and mediawiki

I want to use the Wikipedia API to find the French pages including the ''SQLTemplate:Infobox Scientifique'' missing in the English version. So, my idea was to process the following document with xproc:
http://fr.wikipedia.org/w/api.php?action=query&format=xml&list=embeddedin&eititle=Template:Infobox%20Scientifique&eilimit=400
and the following xslt stylesheet:
<?xml version='1.0' ?>
<xsl:stylesheet
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
version='1.0'
>
<xsl:output method='text' indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="api"/>
</xsl:template>
<xsl:template match="api">
<xsl:for-each select="query/embeddedin/ei">
<xsl:variable name="title" select="translate(#title,&apos; &apos;,&apos;_&apos;)"/>
<xsl:variable name="english-title">
<xsl:call-template name="englishTitle"><xsl:with-param name="title" select="#title"/></xsl:call-template>
</xsl:variable>
<xsl:value-of select="$english-title"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="englishTitle">
<xsl:param name="title"/>
<xsl:variable name="uri1" select="concat(&apos;http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles=&apos;,translate($title,&apos; &apos;,&apos;_&apos;))"/>
<xsl:message><xsl:value-of select="$uri1"/></xsl:message>
<xsl:message>count=<xsl:value-of select="count(document($uri1,/api/query/pages/page/langlinks/ll))"/></xsl:message>
</xsl:template>
</xsl:stylesheet>
The XSLT extract all the articles containing the Template and for each article I wanted to call Wikipedia to get the links between the wikis. Here the template englishTitle calls the xpath function document().
But it always says that count(ll)=1 whereas there are plenty nodes. (e.g. http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles=Carl_Sagan ).
Can't I process the nodes returned by the document() function?
You should try:
<xsl:value-of select="count(document($uri1)/api/query/pages/page/langlinks/ll)"/>
On a different note - what is
translate(#title,&apos; &apos;,&apos;_&apos;)
supposed to mean? What's wrong with:
translate(#title, ' ', '_')
There is no need to encode single quotes in XML attributes unless you want to use a type of quote that delimits the attribute value. All of these are valid:
name="foo"'foo"
name='foo&apos;"foo'
Your entire transformation can be reduced to something like this:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:param name="baseUrl" select="'http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles='" />
<xsl:template match="ei">
<xsl:variable name="uri" select="concat($baseUrl ,translate(#title,' ','_'))"/>
<xsl:variable name="doc" select="document($uri)"/>
<xsl:value-of select="$uri"/>
<xsl:text>
</xsl:text>
<xsl:text>count=</xsl:text>
<xsl:value-of select="count($doc/api/query/pages/page/langlinks/ll)"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Let the XSLT default templates work for you - they do all of the recursion in the background, all you have to do is catch the nodes you want to process (and prevent output of unnecessary text by overriding the default text() template with an empty one).