I have XML like:
<Answers>
<QuestionAnswer Id="1" Answer="أقل من 16 عام"/>
<QuestionAnswer Id="2" Answer="17 – 24 عامً"/>
<QuestionAnswer Id="3" Answer="25- 34 عامً"/>
<QuestionAnswer Id="4" Answer="35- 44 عامً"/>
<QuestionAnswer Id="5" Answer="أكثر من 45 عامً"/>
</Answers>
XSL:
<xsl:for-each select=".//QuestionAnswer">
<xsl:variable name="AnswerId" select="#Id"/>
<xsl:variable name="AnswerText" select="#Answer"/>
<xsl:value-of select="concat(' ',$AnswerId, ' "', $AnswerText, '"')" />
</xsl:for-each>
and return should be the order from XML, but is in an odd order because of Arabic characters.
How can I fix this?
1 "أقل من 16 عام"
2 "17 – 24 عامً"
3 "25- 34 عامً"
4 "35- 44 عامً"
5 "أكثر من 45 عامً"
If you want to sort on the value of #Answer then you should be able to use
<xsl:for-each select=".//QuestionAnswer">
<xsl:sort select="#Answer" lang="ar"/>
You can further qualify the language code, e.g. lang="ar-EG" gives sorting according to the rules for Arabic as used in Egypt. I don't know anything about Arabic so I don't know if this is necessary.
Of course, it might be that your XSLT processor does not support all possible language codes.
Try adding the RTL unicode to your sentence:
<xsl:variable name="rightToLeftUnicode" select="''"></xsl:variable>
<xsl:value-of select="concat(' ',$AnswerId, ' "', $AnswerText, '"', $rightToLeftUnicode)" />
Related
My XML.
<a>
<range nr="1" no-range="1 2 3 4" oc="4"/>
<range nr="2" no-range="41 42 43 44" oc="4"/>
<range nr="3" no-range="43 44 45 46" oc="4"/>
<range nr="4" no-range="50 51 52 53" oc="4"/>
<range nr="5" no-range="53 54" oc="2"/>
<range nr="6" no-range="60 61" oc="2"/>
</a>
I was trying this but not sure how to compare two arrays and print the difference:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:template match="a">
<xsl:variable name="bRannge">
<xsl:for-each select="range">
<xsl:variable name="aRange" select="tokenize(concat(#no-range, ' '), ' ')"/>
<xsl:value-of select="$aRange"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cRange" select="tokenize(normalize-space($bRannge), ' ')"/>
<xsl:variable name="aSeq">
<xsl:for-each select="1 to xs:integer(number($cRange[last()]))">
<xsl:value-of select="position()"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="bSeq" select="tokenize(normalize-space($aSeq), ' ')"/>
[<xsl:value-of select="$cRange"/>]
[<xsl:value-of select="$bSeq"/>]
<!-- How to compare two arrays -->
</xsl:template>
How can I get following report using xslt 2.0:
Number ranges not in use 5-40, 47-49, 55-59
'nr 3' is overlapping with 'nr 2' overlapping number = 43, 44
'nr 5' is overlapping with 'nr 4' overlapping number = 53
You can create each sequence you have with a single expression and then compare them as follows:
<xsl:template match="a">
<xsl:variable name="seq1" as="xs:integer*"
select="for $s in range/#no-range/tokenize(., '\s+')
return xs:integer($s)"/>
<xsl:variable name="seq2" as="xs:integer*"
select="1 to $seq1[last()]"/>
[<xsl:value-of select="$seq1"/>]
[<xsl:value-of select="$seq2"/>]
[<xsl:value-of select="$seq2[not(. = $seq1)]"/>]
</xsl:template>
That outputs the sequence of integers not in your no-range attribute values:
[5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 47 48 49 55 56 57 58 59]
As for finding consecutive ranges, one way would be to use grouping as follows:
<xsl:template match="a">
<xsl:variable name="seq1" as="xs:integer*"
select="for $s in range/#no-range/tokenize(., '\s+')
return xs:integer($s)"/>
<xsl:variable name="seq2" as="xs:integer*"
select="1 to $seq1[last()]"/>
[<xsl:value-of select="$seq1"/>]
[<xsl:value-of select="$seq2"/>]
<xsl:variable name="seq3" as="xs:integer*"
select="$seq2[not(. = $seq1)]"/>
[<xsl:value-of select="$seq3"/>]
<xsl:for-each-group select="$seq3" group-adjacent=". - position()">
[<xsl:value-of select="current-group()[1], current-group()[last()]"
separator=" - "/>]
</xsl:for-each-group>
which then gives
[5 - 40]
[47 - 49]
[55 - 59]
is there in XSLT some function or somethg to alow format digits
like eg 1 will become 01
so if I have
<a>10</a>
<b>5</b>
they will apear as
A10
and
B05
Function Formating numbers:
<xsl:template match="root/*">
<xsl:value-of select="format-number(.,'00')"/>
</xsl:template>
XSLT 1.0 uppercase of nodenames:
<xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:value-of select="translate(name(), $smallcase, $uppercase)" />
Combined:
<xsl:value-of select="concat(translate(name(), $smallcase, $uppercase), format-number(.,'00'))" />
format-number(5, '00')
returns "05".
Similarly,
format-number(10, '00')
returns "10".
Looking at the available axes in XSLT I had to find out that there is no sibling axis which would be the union of preceding-sibling and following-sibling. To me this is a little surprising since I already wrote one answer (XSLT issue...CSV issue.?) in which this axis would have been helpful (although I only have about 10 answers so far). Of course, it is obvious that you can always solve the problem by using the union. So this axis is not really required. But it would be very handy every once in a while and like all the other axes IMHO it would make the code more readable and easier to maintain.
Does anybody know why this axis was left out? Is there maybe a non-obvious reason for this?
By the way: I found at least one issue on StackExchange with a warning about a potential performance degrade using the preceding-sibling and following-sibling axes. But I assume this is true for all the axes containing a substantial portion of the XML tree is used in a nested way. So the reason for omission could not have been due to performance.
Since there has been no activity with this question for a while I would like to answer it myself. Picking up one thought in the comments, it is, of course, hard to retrospectively say why the people responsible of the XSLT 1.0 specification omitted the sibling axis.
One of the most conclusive reasons could have been related to the comments by #JLRiche and #MichaelKay: axis are supposed to go into a specific direction with respect to the reference node and it may be difficult to determine what the direction for sibling would be.
In order to investigate this a little further I set up a test XSLT and a test input XML to check how the axes work (see further below) and in particular what the order of the nodes in the axes are. The result was surprising to me:
The preceding-sibling axes does not start at the node closest to the reference node but with node closest to the start of the document.
The following-sibling does start at the reference node.
This would actually allow to define
sibling := preceding-sibling | following-sibling
with the nodes in this set being continuously iterated from the beginning of the document to the end. There would be no "jump".
The suggested alternative
../node except .
also works well and yields the same set in the same ordering. However, looking at an unfamiliar XSLT I would assume that a sibling axis would explain the logic better than using the parent-children construct.
Interestingly, the fact that axes do not start at the node closest to the reference node but at the node closest the beginning of the document also applies to preceding and ancestor so for example ancester::node[1] does not return the parent of the node but the root node.
The original motivation for me to ask the question was related to not having to repeat a lengthy CONDITION imposed on the attributes of the nodes, e.g. I did not want to write
preceding-sibling::node[CONDITION] | following-sibling::node[CONDITION]
However, since the expression above can be rewritten as
(preceding-sibling::node | following-sibling::node)[CONDITION]
the disadvantage of having to use two axes instead of a sibling axis is not as bad as thought. Of course, in XSLT 2.0 this also works for
(../node except .)[CONDITION]
So, to answer my question: I don't think there is a good reason not to define a sibling axis. I guess nobody thought of it. :-)
Test Setup
This XML test input
<?xml version="1.0" encoding="ISO-8859-1"?>
<node id="1">
<node id="2">
<node id="3">
<node id="4"/>
<node id="5"/>
<node id="6"/>
</node>
<node id="7">
<node id="8"/>
<node id="9"/>
<node id="10"/>
</node>
<node id="11">
<node id="12"/>
<node id="13"/>
<node id="14"/>
</node>
</node>
<node id="15">
<node id="16">
<node id="17"/>
<node id="18"/>
<node id="19"/>
</node>
<node id="20">
<node id="21"/>
<node id="22"/>
<node id="23"/>
</node>
<node id="24">
<node id="25"/>
<node id="26"/>
<node id="27"/>
</node>
</node>
<node id="28">
<node id="29">
<node id="30"/>
<node id="31"/>
<node id="32"/>
</node>
<node id="33" value="A">
<node id="34"/>
<node id="35"/>
<node id="36"/>
</node>
<node id="37">
<node id="38"/>
<node id="39"/>
<node id="40"/>
</node>
<node id="41">
<node id="42"/>
<node id="43"/>
<node id="44"/>
</node>
<node id="45" value="A">
<node id="46"/>
<node id="47"/>
<node id="48"/>
</node>
</node>
</node>
using this XSLT 2.0 sheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:variable name="id" select="'37'"/>
<xsl:template name="dump">
<xsl:text> </xsl:text>
<xsl:value-of select="#id"/>
</xsl:template>
<xsl:template match="//node[#id = $id]">
<xsl:text>preceding siblings: </xsl:text>
<xsl:for-each select="preceding-sibling::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
following siblings: </xsl:text>
<xsl:for-each select="following-sibling::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
preceding and following siblings: </xsl:text>
<xsl:for-each select="preceding-sibling::node | following-sibling::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
preceding and following siblings with value A: </xsl:text>
<xsl:for-each select="(preceding-sibling::node | following-sibling::node)[#value = 'A']">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
following siblings: </xsl:text>
<xsl:for-each select="following-sibling::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
parent's children: </xsl:text>
<xsl:for-each select="../node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
parent's children except self: </xsl:text>
<xsl:for-each select="../node except .">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
parent's children except self with value A: </xsl:text>
<xsl:for-each select="(../node except .)[#value = 'A']">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
ancestors: </xsl:text>
<xsl:for-each select="ancestor::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
immediate ancestor: </xsl:text>
<xsl:for-each select="(ancestor::node)[1]">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
ancestors or self: </xsl:text>
<xsl:for-each select="ancestor-or-self::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
descendants: </xsl:text>
<xsl:for-each select="descendant::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
descendants or self: </xsl:text>
<xsl:for-each select="descendant-or-self::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
preceding: </xsl:text>
<xsl:for-each select="preceding::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
<xsl:text>
following: </xsl:text>
<xsl:for-each select="following::node">
<xsl:call-template name="dump"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
will yield this output
preceding siblings: 29 33
following siblings: 41 45
preceding and following siblings: 29 33 41 45
preceding and following siblings with value A: 33 45
following siblings: 41 45
parent's children: 29 33 37 41 45
parent's children except self: 29 33 41 45
parent's children except self with value A: 33 45
ancestors: 1 28
immediate ancestor: 1
ancestors or self: 1 28 37
descendants: 38 39 40
descendants or self: 37 38 39 40
preceding: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 29 30 31 32 33 34 35 36
following: 41 42 43 44 45 46 47 48
I’m new in the xslt topic and have a problem that can't solve on my own.
Here e excample of my xml file:
<node>
<failure><![CDATA[
some useless information.
CRS urn:ogc:def:crs:EPSG::25830 not defined.
CRS urn:ogc:def:crs:EPSG::25833 not possible.
CRS urn:ogc:def:crs:EPSG::25830 not defined.
some useless information.]]>
</failure>
</node>
The main problem is that the information stand in a CDATA block and many different informations are mixed up. I have found a way to get them out, but only as a string value not able to differentiate between the sort.
I need a way to extract elements that fit the pattern: "CRS [-unknown-] [id] not [result]"
What i want is something like this:
<failure>
<CRS>
<id> urn:ogc:def:crs:EPSG::25830 </id>
<result> not defined </result>
</CRS>
<CRS>
<id> urn:ogc:def:crs:EPSG::25833 </id>
<result> not posible </result>
</CRS>
<CRS>
<id> urn:ogc:def:crs:EPSG::25830 </id>
<result> not defined </result>
</CRS>
</failure>
Can somebody help me or made experience with simular problems?
XSLT 2.0 has xsl:analyze-string which is designed for precisely this task, so if at all possible I suggest you upgrade to a 2.0 processor such as Saxon:
<xsl:template match="node">
<failure>
<xsl:analyze-string select="failure"
regex="^\s*CRS\s*(\S+)\s*(not\s*.*)$" flags="m">
<xsl:matching-substring>
<CRS>
<id><xsl:value-of select="regex-group(1)" /></id>
<result><xsl:value-of select="normalize-space(regex-group(2))" /></result>
</CRS>
</xsl:matching-substring>
</xsl:analyze-string>
</failure>
</xsl:template>
String manipulation facilities in XSLT 1.0 are extremely limited in comparison, and since XSLT is a functional language without updateable variables you'd have to write some sort of hideously complex set of recursive call-template logic to split up the text into separate lines and then extract the relevant bits out of each line in turn using substring-before and substring-after.
<xsl:template name="each-line">
<xsl:param name="val" />
<!-- pull out everything before the first newline and normalize (trim leading
and trailing whitespace and squash internal whitespace to a single space
character -->
<xsl:variable name="firstLine"
select="normalize-space(substring-before($val, '
'))" />
<!-- pull out everything after the first newline -->
<xsl:variable name="rest" select="substring-after($val, '
')" />
<xsl:if test="$firstLine">
<xsl:call-template name="process-line">
<xsl:with-param name="line" select="$firstLine" />
</xsl:call-template>
</xsl:if>
<!-- if there are still some non-empty lines left then process them recursively -->
<xsl:if test="normalize-space($rest)">
<xsl:call-template name="each-line">
<xsl:with-param name="val" select="$rest" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="process-line">
<xsl:param name="line" />
<xsl:if test="starts-with($line, 'CRS ') and contains($line, ' not ')">
<!-- here $line will be something like
"CRS urn:ogc:def:crs:EPSG::25830 not defined." -->
<CRS>
<!-- ID is everything between the first and second spaces -->
<id><xsl:value-of select="substring-before(substring-after($line, ' '), ' ')" /></id>
<!-- result is everything after the second space -->
<result><xsl:value-of select="substring-after(substring-after($line, ' '), ' ')" /></result>
</CRS>
</xsl:if>
</xsl:template>
You would call this logic using a construct like
<xsl:template match="node">
<failure>
<xsl:call-template name="each-line">
<xsl:with-param name="val" select="failure" />
</xsl:call-template>
</failure>
</xsl:template>
Regarding Umbraco XSLT version 1.
I have aprox. 150 news items in XML. Lets say like this (all is pseudocode until I get more familiar with this xml/xslt):
<news>
<data alias=date>2008-10-20</data>
</news>
<news>
<data alias=date>2009-11-25</data>
</news><news>
<data alias=date>2009-11-20</data>
</news> etc. etc....
I would like to run through the XML and create html-output as a news archive. Something like (tags not important):
2008
Jan
Feb
...
2009
Jan
Feb
Mar
etc. etc.
I can only come up with a nested for-each (pseudocode):
var year_counter = 2002
var month_counter = 1
<xsl:for-each select="./data [#alias = 'date']=year_counter">
<xsl:for-each select="./data [#alias = 'date']=month_counter">
<xsl:value-of select="data [#alias = 'date']>
"...if month_counter==12 end, else month_counter++ ..."
</xsl:for-each>
"... year_counter ++ ..."
</xsl:for-each>
But a programmer pointet out that looping through 10 years will give 120 loops and that is bad coding. Since I think Umbraco caches the result I am not so concerned, plus in this case there will be a max. of 150 records.
Any clues on how to sort and output many news items and group them in year and group each year in months?
Br. Anders
For the following solution I used this XML file:
<root>
<news>
<data alias="date">2008-10-20</data>
</news>
<news>
<data alias="date">2009-11-25</data>
</news>
<news>
<data alias="date">2009-11-20</data>
</news>
<news>
<data alias="date">2009-03-20</data>
</news>
<news>
<data alias="date">2008-01-20</data>
</news>
</root>
and this XSLT 1.0 transformation:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cfg="http://tempuri.org/config"
exclude-result-prefixes="cfg"
>
<xsl:output method="xml" encoding="utf-8" />
<!-- index news by their "yyyy" value (first 4 chars) -->
<xsl:key
name="kNewsByY"
match="news"
use="substring(data[#alias='date'], 1, 4)"
/>
<!-- index news by their "yyyy-mm" value (first 7 chars) -->
<xsl:key
name="kNewsByYM"
match="news"
use="substring(data[#alias='date'], 1, 7)"
/>
<!-- translation table (month number to name) -->
<config xmlns="http://tempuri.org/config">
<months>
<month id="01" name="Jan" />
<month id="02" name="Feb" />
<month id="03" name="Mar" />
<month id="04" name="Apr" />
<month id="05" name="May" />
<month id="06" name="Jun" />
<month id="07" name="Jul" />
<month id="08" name="Aug" />
<month id="09" name="Sep" />
<month id="10" name="Oct" />
<month id="11" name="Nov" />
<month id="12" name="Dec" />
</months>
</config>
<xsl:template match="root">
<xsl:copy>
<!-- group news by "yyyy" -->
<xsl:apply-templates mode="year" select="
news[
generate-id()
=
generate-id(key('kNewsByY', substring(data[#alias='date'], 1, 4))[1])
]
">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- year groups will be enclosed in a <year> element -->
<xsl:template match="news" mode="year">
<xsl:variable name="y" select="substring(data[#alias='date'], 1, 4)" />
<year num="{$y}">
<!-- group this year's news by "yyyy-mm" -->
<xsl:apply-templates mode="month" select="
key('kNewsByY', $y)[
generate-id()
=
generate-id(key('kNewsByYM', substring(data[#alias='date'], 1, 7))[1])
]
">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</year>
</xsl:template>
<!-- month groups will be enclosed in a <month> element -->
<xsl:template match="news" mode="month">
<xsl:variable name="ym" select="substring(data[#alias='date'], 1, 7)" />
<xsl:variable name="m" select="substring-after($ym, '-')" />
<!-- select the label of the current month from the config -->
<xsl:variable name="label" select="document('')/*/cfg:config/cfg:months/cfg:month[#id = $m]/#name" />
<month num="{$m}" label="{$label}">
<!-- process news of the current "yyyy-mm" group -->
<xsl:apply-templates select="key('kNewsByYM', $ym)">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</month>
</xsl:template>
<!-- for the sake of this example, news elements will just be copied -->
<xsl:template match="news">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
When the transformation is applied, the following output is produced:
<root>
<year num="2009">
<month num="11" label="Nov">
<news>
<data alias="date">2009-11-25</data>
</news>
<news>
<data alias="date">2009-11-20</data>
</news>
</month>
<month num="03" label="Mar">
<news>
<data alias="date">2009-03-20</data>
</news>
</month>
</year>
<year num="2008">
<month num="10" label="Oct">
<news>
<data alias="date">2008-10-20</data>
</news>
</month>
<month num="01" label="Jan">
<news>
<data alias="date">2008-01-20</data>
</news>
</month>
</year>
</root>
It has the right structure already, you can adapt actual appearance to your own needs.
The solution is a two-phase Muenchian grouping approach. In the first phase, news items are grouped by year, in the second phase by year-month.
Please refer to my explanation of <xsl:key> and key() over here. You don't need to read the other question, though it is a similar problem. Just read the lower part of my answer.
What you need is the so-called Muenchian Grouping method, which addresses exactly this problem/pattern for XSLT.
Basically, it groups by finding unique keys and looping over the entries contained in the key being used.
in addition to lucero, check out Xsl grouping duplicates problem for avoiding problems with month names being removes
You can't do month_counter++ in XSLT, it's not a procedural language and it's not how XSLT works. So, it's kind of pointless to worry about this being inefficient if this does not work this way.
This looks like a major pain in the neck in XSLT. My XSLT is not fresh enough to try and actually implement it. But here are two ways:
1)
use xsl:key to extract all unique years-
then iterate through these years. For each year do
use xsl:key to extract all months
For each month do
2) (seems easier, if it works.)
sort them by date, save sorted array in variable
iterate this variable (it's important that variable holds sorted array)
each time look at preceding-sibling. If its year/month not equal to the current element, write the appropriate header
3) Forget XSLT, use a real programming language.