I have an xml file which I need to group-by using xsl:for-each-group. Everything works fine but the problem comes from when there is elements which have white space at the end of them (e.g. <word>test </word> and <word>test</word>) but I need these to be considered as one group.
Here's the sample xml file:
<u>
<s>
<w>this </w>
<w>is </w>
<w>a </w>
<w>test </w>
</s>
<s>
<w>this</w>
<w>is</w>
<w>a</w>
<w>test</w>
</s>
<u>
Here's the xslt code
<xsl:for-each-group select="bncDoc/stext/div/u/s" group-by="w" >
<tr>
<td style="text-align: center;">
<xsl:value-of select="current-grouping-key()"/>
</td>
<td>
<xsl:value-of select="count(current-group())"/>
</td>
</tr>
</xsl:for-each-group>
Is there any workaround for this?
<xsl:for-each-group select="bncDoc/stext/div/u/s/w" group-by="normalize-space()">
<!-- ... -->
</xsl:for-each-group>
OK, found the answer:
You just need to use the normailize-space() in this way:
<xsl:for-each-group select="bncDoc/stext/div/u/s/w" group-by="normalize-space((text())">
.
.
.
</xsl:for-each-group>
Related
I've just started learning XML/XSL and I've hit a roadblock in one of my assignments. Tried Googling and searching over here but I can't seem to find a question that has a solution that is basic. so what I am trying is to display rows of bucket-type and room-types associated with it. can somebody please help
<list-inventory list-count="2">
<list list-type="Standard" list-order = "1" count-Types = "3">
<types type="BEN2D"></room>
<types type="BESH2D"></room>
<types type="HNK"></room>
</list>
<list list-type="Deluxe" list-order = "2" count-Types = "3">
<types type="SNK"></room>
<types type="TESTKD"></room>
<types type="TESTKD"></room>
</list>
<list-inventory>
I want table as below
Standard | Deluxe
BEN2D |SNK
BESH2D |TESTKD
HNK |TESTKD
I tried below xsl code but i see all list-type in single column and only 1st is being printing for all list-type:
<xsl:for-each select="/contents/list-inventory/list">
<tr>
<td class="alt-th" style="border:1px solid black">
<xsl:value-of select="#list-type"/>
</td>
</tr>
<tr>
<td style="border:1px solid black">
<xsl:for-each select="/contents/list-inventory/list/types">
<span><xsl:value-of select="#type"/></span>
<xsl:if test="position()!=last()">
<br/>
</xsl:if>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
Can someone help me with xsl:for-each inside a xsl:for-each
It's too bad you did not post your expected result as code. I would assume that you want a separate row for each pair of values. As I stated in the comments, this is far from being trivial.
However, you could make it simpler if you are willing to settle for a single data row, where each cell contains all the values of the corresponding list (your attempt seems to suggest that this is what you actually tried to accomplish).
So, given a well-formed XML input:
XML
<list-inventory list-count="2">
<list list-type="Standard" list-order = "1" count-Types = "3">
<types type="BEN2D"/>
<types type="BESH2D"/>
<types type="HNK"/>
</list>
<list list-type="Deluxe" list-order = "2" count-Types = "3">
<types type="SNK"/>
<types type="TESTKD"/>
<types type="TESTKD"/>
</list>
</list-inventory>
you could do simply:
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="/list-inventory">
<table border="1">
<!-- header -->
<tr>
<xsl:for-each select="list">
<th>
<xsl:value-of select="#list-type"/>
</th>
</xsl:for-each>
</tr>
<!-- body -->
<tr>
<xsl:for-each select="list">
<td>
<xsl:for-each select="types">
<xsl:value-of select="#type"/>
<br/>
</xsl:for-each>
</td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
to get:
Result
<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
<tr>
<th>Standard</th>
<th>Deluxe</th>
</tr>
<tr>
<td>BEN2D<br/>BESH2D<br/>HNK<br/>
</td>
<td>SNK<br/>TESTKD<br/>TESTKD<br/>
</td>
</tr>
</table>
which would render as:
I'm not sure how general you want your solution to be (i.e what inputs does it have to handle other than the example shown), but I would do something like:
<xsl:template match="list-inventory">
<xsl:variable name="list2" select="list[2]/types"/>
<xsl:for-each select="list[1]/types">
<xsl:variable name="position" select="position()"/>
<tr>
<td><xsl:value-of select="#type"/></td>
<td><xsl:value-of select="$list2[$position]/#type"/></td>
</tr>
</xsl:for-each>
</xsl:template>
Both the variables here are needed to avoid problems with context: the effect of an XPath expression depends on the context at the time it is evaluated, so you can evaluate a variable to capture information at the time you're in the right context.
I woud like to make some nested loops over my xml doc
Here is my xml
<evolutionlist>
<date id="22_05_2014">
<objet>
<identifier>1VD5-3452-8R5</identifier>
<link>Link1</link>
<title>EXCHANGE OF ELEMENTS</title>
</objet>
<objet>
<identifier>1V24-34A2-8C5</identifier>
<link>Link1</link>
<title>NEW ELEMENT</title>
</objet>
</date>
<date id="21_05_2014">
<identifier>1VV4-34A2-8C5</identifier>
<link>Link2</link>
<title>REPLACE</title>
</date>
</evolutionlist>
Ideally, I woudl like to display something like
22_05_2014
objet1 (with add infos)
objet2 (with add infos)
21_05_2014
objet3 (with add infos)
I made:
<xsl:for-each select="//date">
<xsl:value-of select="#id"/>
<xsl:for-each select="objet">
<tr>
<td>
<xsl:value-of select="identifier"/>
</td>
<td>
<xsl:value-of select="link"/>
</td>
<td>
<xsl:value-of select="title"/>
</td>
</tr>
</xsl:for-each>
</xsl:for-each>
but I got
22_05_2014 21_05_2014
objet1
objet2
objet3
where did I go wrong ?
EDIT
I tried
<xsl:for-each select="./objet">
for the second loop but that did not work either
Shame on me !
I forgot to do this:
<tr>
<td>
<xsl:value-of select="./#id"/>
</td>
</tr>
You were right, Ian Roberts, by encouraging me do write the real code
I come across this small problem while creating a xslt file... I have this generic xml file:
<data>
<folder>
<file>
<name>file1</name>
<date>2000</date>
<index1>1</index1>
<index2>1</index2>
</file>
<file>
<name>file2</name>
<date>2001</date>
<index1>1</index1>
<index2>1</index2>
</file>
<file>
<name>file3</name>
<date>2004</date>
<index1>2</index1>
<index2>1</index2>
</file>
</folder>
</data>
Given this abstract example, I have to transform it into something like:
<table>
<tr>
<td>Name</td>
<td>Date</td>
</tr>
<tr>
<td>file1</td>
<td>2000</td>
</tr>
<tr>
<td>file2</td>
<td>2001</td>
</tr>
</table>
<table>
<tr>
<td>Name</td>
<td>Date</td>
</tr>
<tr>
<td>file3</td>
<td>2004</td>
</tr>
</table>
I have to group the file elements per table based on their index1 and index2 (like an ID pair). I am able to create a table for every separated file, but I can't come with a solution to create a table for every file sharing index1 and index2. Any idea or suggestion?
Since you are using XSLT 2.0 you can use the xsl:for-each-group statement. You have two choices here, depending on whether you wish to keep groups together and respect the sequence or whether you just want to group regardless of sequence.
That is, given aabaab would you want groups of (aaaa, bb) or (aa, b, aa, b)?
This first groups all file elements with the same index1 and index2 regardless of order in the document (I've put in the body element just to make it well-formed)
<xsl:template match="folder">
<body>
<xsl:for-each-group select="file" group-by="concat(index1, '-', index2)">
<!-- xsl:for-each-group sets the first element in the group as the context node -->
<xsl:apply-templates select="."/>
</xsl:for-each-group>
</body>
</xsl:template>
<xsl:template match="file">
<table>
<tr>
<td>Name</td>
<td>Date</td>
</tr>
<xsl:apply-templates select="current-group()" mode="to-row"/>
</table>
</xsl:template>
<xsl:template match="file" mode="to-row">
<tr>
<xsl:apply-templates select="name|date"/>
</tr>
</xsl:template>
<xsl:template match="name|date">
<td><xsl:apply-templates/></td>
</xsl:template>
The second version would only need the first template changed to:
<xsl:template match="folder">
<body>
<xsl:for-each-group select="file" group-adjacent="concat(index1, '-', index2)">
<xsl:apply-templates select="."/>
</xsl:for-each-group>
</body>
</xsl:template>
I have an XML file with a list of items with two different qualities and I need to create an HTML output that list the items in the two categories with a numbering sequence that start with a on both. I cannot find a solution. Here are the files I created so far:
XML
<?xml version="1.0" encoding="UTF-8"?>
<refrigerator>
<item>
<quality>Good</quality>
<item_name>eggs</item_name>
</item>
<item>
<quality>Good</quality>
<item_name>chess</item_name>
</item>
<item>
<quality>Good</quality>
<item_name>soda</item_name>
</item>
<item>
<quality>Bad</quality>
<item_name>chicken meat</item_name>
</item>
<item>
<quality>Bad</quality>
<item_name>spinach</item_name>
</item>
<item>
<quality>Bad</quality>
<item_name>potatoes</item_name>
</item>
</refrigerator>
XSL
<table width="100%" border="1">
<tr>
<td>
<strong>These are the good items in the refrigerator/strong>
<xsl:for-each select="refrigerator/item">
<xsl:if test="quality = 'Good'">
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name"/>
</xsl:if>
</xsl:for-each>
, <strong>and these are the bad ones/strong>
<xsl:for-each select="refrigerator/item">
<xsl:if test="quality = 'Bad'">
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name"/>
</xsl:if>
</xsl:for-each>
. Some more text over here.</td>
</tr>
</table>
HTML
These are the good items in the refrigerator:a) eggs b) chess c) soda , and these are the bad ones:d) chicken meat e) spinach f) potatoes . Some more text over here.
OUTPUT needed
These are the good items in the refrigerator:a) eggs b) chess c) soda , and these are the bad ones:a) chicken meat b) spinach c) potatoes . Some more text over here.
Any help is greatly appreciate it.
Regards.
A.
Your problem is that position() is sensitive to exactly what list of nodes you're currently for-eaching over. Instead of
<xsl:for-each select="refrigerator/item">
<xsl:if test="quality = 'Good'">
put the test in the for-each select expression
<xsl:for-each select="refrigerator/item[quality = 'Good']">
and similarly for the "Bad" case.
As Tomalak suggests you can save repeating the same code in the two cases by moving it to a separate template and using apply-templates instead of for-each.
Either: Use <xsl:for-each> correctly.
<xsl:template match="refrigerator">
<table width="100%" border="1">
<tr>
<td>
<strong>These are the good items in the refrigerator</strong>
<xsl:for-each select="item[quality = 'Good']">
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name" />
</xsl:for-each>
<xsl:text>, <xsl:text>
<strong>and these are the bad ones</strong>
<xsl:for-each select="item[quality = 'Bad']">
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name" />
</xsl:for-each>
<xsl:text>. Some more text over here.</xsl:text>
</td>
</tr>
</table>
</xsl:template>
Or, don't repeat yourself and don't use <xsl:for-each> at all.
<xsl:template match="refrigerator">
<table width="100%" border="1">
<tr>
<td>
<strong>These are the good items in the refrigerator</strong>
<xsl:apply-templates select="item[quality = 'Good']" mode="numbered" />
<xsl:text>, <xsl:text>
<strong>and these are the bad ones</strong>
<xsl:apply-templates select="item[quality = 'Bad']" mode="numbered" />
<xsl:text>. Some more text over here.</xsl:text>
</td>
</tr>
</table>
</xsl:template>
<xsl:template match="item" mode="numbered">
<div>
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name" />
</div>
</xsl:template>
Or, and this is even more preferred, use HTML numbered lists. Output <ol> and <li> and style them via CSS, instead of hard-coding list numbers in your output.
i have a XML datadump from a database that i want to present using xslt.
The structure of the data is not 'straightforward' for the layout that i want (i also use the XML data for another report).
What i want is to do some calculations on data from Level-A where i need to group on Level-C children.
I know i can probably select the data again into an XML file where the structure is 'easy' for my report, but that is my last resort because i have the feeling that it can also be accomplished in XSLT itself. Most probbaly i need some 'Muenchian' trick to get it done, but since i am a 'Muenchian Virgin' i get stuck in every attempt (that i try to 'steal' and change ...).
Does anybody know if Muenchian is the way to proceed and can somebody help me to get on the right track ?
I did some reading (including Jeni Tennison's), but the stuff i have seen so far is not covering my problem as far as i know ...
Below is a simplified XML structure that is (more or less) representative for my real problem.
Any ideas?
Kind regards, Henk
Simplyfied XML:
<data>
<a>
<a_id>A1</a_id>
<a_desc>A one</a_desc>
<a_val>1</a_val>
<b>
<c>
<c_id>C2</c_id>
<c_desc>C two</c_desc>
</c>
</b>
</a>
<a>
<a_id>A2</a_id>
<a_desc>A two</a_desc>
<a_val>2</a_val>
<b>
<c>
<c_id>C2</c_id>
<c_desc>C two</c_desc>
</c>
</b>
</a>
<a>
<a_id>A3</a_id>
<a_desc>A three</a_desc>
<a_val>3</a_val>
<b>
<c>
<c_id>C1</c_id>
<c_desc>C one</c_desc>
</c>
</b>
</a>
<a>
<a_id>A4</a_id>
<a_desc>A four</a_desc>
<a_val>7</a_val>
<b>
<c>
<c_id>C3</c_id>
<c_desc>C three</c_desc>
</c>
</b>
</a>
<a>
<a_id>A5</a_id>
<a_desc>A five</a_desc>
<a_val>11</a_val>
<b>
<c>
<c_id>C1</c_id>
<c_desc>C one</c_desc>
</c>
</b>
</a>
</data>
Required output should be something like:
C_desc Count() Sum(a_val) Avg(a_val)
------ ------- ---------- ----------
C one 3 15 5
C two 1 2 2
C three 1 7 7
As you mention, Muenchian grouping is the way to go (in XSLT1.0). You say you want to group a elements, using values in a c element. Therefore you would define a key like so:
<xsl:key name="a" match="a" use="b/c/c_desc" />
Then, you need to get 'distinct' a elements, which is done by selecting a elements that happen to be the first element in the group for a given key. You do this with this fairly scary expression
<xsl:apply-templates
select="//a[generate-id() = generate-id(key('a', b/c/c_desc)[1])]" />
Here, key('a', b/c/c_desc)[1] will find the first element in the key's group, and then you use generate-id to compare the elements.
Then, you would have a template to match the distinct a elements, and within in this you can then do calculations on the group. For example, to get the sum:
<xsl:value-of select="sum(key('a', b/c/c_desc)/a_val)" />
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="exsl">
<xsl:output method="html" indent="yes"/>
<xsl:key name="a" match="a" use="b/c/c_desc" />
<xsl:template match="/">
<table>
<tr>
<td>C_desc</td>
<td>Count</td>
<td>Sum</td>
<td>Avg</td>
</tr>
<xsl:apply-templates select="//a[generate-id() = generate-id(key('a', b/c/c_desc)[1])]" />
</table>
</xsl:template>
<xsl:template match="a">
<xsl:variable name="c_desc" select="b/c/c_desc" />
<tr>
<td><xsl:value-of select="count(key('a', $c_desc))" /></td>
<td><xsl:value-of select="sum(key('a', $c_desc)/a_val)" /></td>
<td><xsl:value-of select="sum(key('a', $c_desc)/a_val) div count(key('a', $c_desc))" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<table>
<tr>
<td>C_desc</td>
<td>Count</td>
<td>Sum</td>
<td>Avg</td>
</tr>
<tr>
<td>3</td>
<td>15</td>
<td>5</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>1</td>
<td>7</td>
<td>7</td>
</tr>
</table>