How to repeat XSLT child nodes of the same name? - xslt

I have an xml dump of a transaction that I want to create a report from.
The (simplified) xml structure looks like:
Subject
id
name
license
event id=key
result
eventtype
date
action
charge id=key
result
code
date
...etc
There is only one id, name, and license for every subject; but there could be many events, and many charges for each subject--each one having a different result.
The report output is a table type, with each subject having a header of name, id, license, and multiple blocks of data transactions listed below the header. Each data block has different values and structure based on the type of transaction.
It works perfectly for the header, and one each of the data transaction types, but only the first event transaction and the first charge transaction will print out.
The (simplified) xsl looks like:
<xsl:for-each select="file/subject">
<xsl:if test="not (#subject = preceding-sibling::subject[1])">
<table width="754" border="2" cellpadding="0">
<tr>
<td width="172">ctn: <strong><xsl:value-of select="id"/></strong></td>
<td width="364">defendant: <strong><xsl:value-of select="name"/></strong></td>
<td width="200">sid: <strong><xsl:value-of select="license"/></strong></td>
</tr>
</table>
</xsl:if>
<blockquote>
<xsl:if test="event/eventType != ' '">
<table width="695" border="1">
<tr>
<td>Event<xsl:value-of select="event/result"/></td>
<td>Eventtype<xsl:value-of select="event/eventtype"/></td>
<td>Date<xsl:value-of select="event/date"/></td>
</tr>
</table>
</xsl:if>
<xsl:if test="charge/code != ' '">
<table width="695" border="1">
<tr>
<td>Charge<xsl:value-of select="charge/result"/></td>
<td>Code<xsl:value-of select="charge/code"/></td>
<td>Date<xsl:value-of select="charge/date"/></td>
</tr>
</table>
</xsl:if>
</blockquote>
</xsl:for-each>
I assume that I need an additional for-each to scan the child nodes, but adding the additional loop just gives me the headers.
Any thoughts?
(and thanks for taking the time to read this far. :o)

Where possible in XSLT, avoid for-each and apply your nodes to transformation templates instead.
I came up with this, which you can run at this playground session - not sure if it's broadly what you're after.
<!-- root and static content -->
<xsl:template match="/">
<xsl:apply-templates select='root/Subject' />
</xsl:template>
<!-- iteration content: subject -->
<xsl:template match='Subject'>
<table>
<tr>
<td>ctn: <strong><xsl:value-of select="id"/></strong></td>
<td>defendant: <strong><xsl:value-of select="name"/></strong></td>
<td>sid: <strong><xsl:value-of select="license"/></strong></td>
</tr>
</table>
<xsl:apply-templates select='event' />
<xsl:apply-templates select='charge' />
</xsl:template>
<!-- iteration content: event -->
<xsl:template match='event'>
<blockquote>
<table>
<tr>
<td>Event: <xsl:value-of select="result"/></td>
<td>Event type: <xsl:value-of select="eventtype"/></td>
<td>Date: <xsl:value-of select="date"/></td>
</tr>
</table>
</blockquote>
</xsl:template>
<!-- iteration content: charge -->
<xsl:template match='charge'>
<blockquote>
<table>
<tr>
<td>Charge: <xsl:value-of select="result"/></td>
<td>Code: <xsl:value-of select="code"/></td>
<td>Date: <xsl:value-of select="date"/></td>
</tr>
</table>
</blockquote>
</xsl:template>

Related

XSL Repeat Template Object containing List<Object>

I have a Library object that contains a collection of Books... The Library object has properties like Name, Address, Phone... While the Book object has properties like ISDN, Title, Author, and Price.
XML looks something like this...
<Library>
<Name>Metro Library</Name>
<Address>1 Post Rd. Brooklyn, NY 11218</Address>
<Phone>800 976-7070</Phone>
<Books>
<Book>
<ISDN>123456789</ISDN>
<Title>Fishing with Luke</Title>
<Author>Luke Miller</Author>
<Price>18.99</Price>
</Book>
<Book>
<ISDN>234567890</ISDN>
<Title>Hunting with Paul</Title>
<Author>Paul Worthington</Author>
<Price>28.99</Price>
</Book>
...
And more books
...
</Books>
</Library>
I have a template with space for only 10 per page for example. There can be hundreds of books in the list of Books... So I need to limit the number of books and repeat the template every 10 books.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<div>
<table>
<tr>
<td>NAME</td>
<td><xsl:value-of select="/Library/Name"/></td>
</tr>
<tr>
<td>ADDRESS</td>
<td><xsl:value-of select="/Library/Address"/></td>
</tr>
<tr>
<td>PHONE</td>
<td><xsl:value-of select="/Library/Phone"/></td>
</tr>
</table>
<table>
<xsl:for-each select="/Library/Books/Book">
<tr>
<td><xsl:value-of select="position()"/></td>
<td><xsl:value-of select="ISDN"/></td>
<td><xsl:value-of select="Title"/></td>
<td><xsl:value-of select="Author"/></td>
<td><xsl:value-of select="Price"/></td>
</tr>
</xsl:for-each>
</table>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
How can I get the Library information to appear on all repeating pages and add 10 books per page?... First page has Library info with Books 1 thru 10, Second page has Library info with Books 11 thru 20, and so on??
Thanks
For starters, try not to use for-each, apply-templates allows the engine to optimise the order that events are processed.
It appears that you are calling this stylesheet from some other system, so the approach I've taken is to define a pagination param. In the host language, when you call this just change the root parameter. This then allows you to select the require pages in this line here:
Books/Book[($page - 1)*10 < position() and position() <= ($page)*10]
This should do the trick.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="page" select="1"/>
<xsl:template match="/Library">
<html>
<body>
<div>
<table>
<tr>
<td>NAME</td>
<td>
<xsl:value-of select="/Name"/>
</td>
</tr>
<tr>
<td>ADDRESS</td>
<td>
<xsl:value-of select="/Address"/>
</td>
</tr>
<tr>
<td>PHONE</td>
<td>
<xsl:value-of select="/Phone"/>
</td>
</tr>
</table>
<table>
<xsl:apply-templates select="Books/Book[($page - 1)*10 < position() and position() <= ($page)*10]"/>
</table>
</div>
</body>
</html>
</xsl:template>
<xsl:template match="/Book">
<tr>
<td>
<xsl:value-of select="position()"/>
</td>
<td>
<xsl:value-of select="ISDN"/>
</td>
<td>
<xsl:value-of select="Title"/>
</td>
<td>
<xsl:value-of select="Author"/>
</td>
<td>
<xsl:value-of select="Price"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>

XSLT: multiple tables

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>

The variable or parameter 'Rows' is either not defined or it is out of scope

How do i achieve this through xslt?
I am getting an error from the cde above given by you
( sharepoint 2010 xslt dataview : modify table structure to display list item )
The error is: The variable or parameter 'Rows' is either not defined or it is out of scope.
Please help why am i getting this error:(
My full code would go like this after the code by you has been added:
<xsl:template match='dsQueryResponse'>
<table cellpadding="10" cellspacing="0" border="1" style="padding:25px;">
<!--table for head-->
<tr>
<!--table row-->
<td colspan='2'>
<!--table definition-->
<b style="font-size:25px;">ELearning List</b>
<!-- heading-->
</td>
</tr>
<xsl:apply-templates select='Rows/Row'/>
</table>
</xsl:template>
<xsl:template match='Row'>
<!-- template is defined above -->
<xsl:for-each select="$Rows[position() mod 3 = 1]">
<!-- 3 recods in one row should be displayed -->
<tr>
<xsl:variable name="i" select="position() - 1" />
<xsl:for-each select="$Rows[(position() > ($i * 3)) and (position() <= (($i + 1) * 3))]">
<td>
<img src="../PublishingImages/FLDRNEW.GIF" width="50px" height="50px" style="padding-right:20px;"></img>
<!-- image is to display folder below which the hyperlinked text has been populated -->
<br/>
<a href="{#FileRef}" style="font-weight:bold;">
<!-- it is the anchor tag which drives to the corresponding documents -->
<xsl:value-of select="substring-after(string(#FileRef),'/Docs/')"/>
<!-- fetches the value of Name column -->
</a>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</xsl:template>
The error is: The variable or parameter 'Rows' is either not defined or it is out of scope.
Please help
Could you give this a try?
<xsl:template match='dsQueryResponse'>
<table cellpadding="10" cellspacing="0" border="1" style="padding:25px;">
<!--table for head-->
<tr>
<!--table row-->
<td colspan='2'>
<!--table definition-->
<b style="font-size:25px;">ELearning List</b>
<!-- heading-->
</td>
</tr>
<xsl:apply-templates
select='Rows/Row[position() mod 3 = 1]' mode="group" />
</table>
</xsl:template>
<xsl:template match='Row' mode="group">
<tr>
<xsl:apply-templates
select=". | following-sibling::Row[position() < 3]" />
</tr>
</xsl:template>
<xsl:template match="Row">
<td>
<!-- image is to display folder below which the hyperlinked text has
been populated -->
<img src="../PublishingImages/FLDRNEW.GIF" width="50px" height="50px"
style="padding-right:20px;" />
<br/>
<!-- the anchor tag which drives to the corresponding documents -->
<a href="{#FileRef}" style="font-weight:bold;">
<!-- fetches the value of Name column -->
<xsl:value-of select="substring-after(string(#FileRef),'/Docs/')"/>
</a>
</td>
</xsl:template>

SharePoint 2010 Add Paging to Custom DataViewWebPart

I have a custom DataViewWebPart to which I would like to add paging. I have included the meat of the XSLT I am using to form the scope of the details for my web part below. Any suggestions on how to implement paging on this item (since I am utilizing keys it's not so clear to me).
<xsl:key name="casebystate" match="Row" use="#StoreState"/>
<xsl:template match="/">
<xsl:variable name="cbs_Rows" select="/dsQueryResponse/Rows/Row/#StoreState"/>
<table border="0" width="100%" cellpadding="2" cellspacing="0">
<tr valign="top">
<th class="ms-vh" nowrap="nowrap">State</th>
<th class="ms-vh" nowrap="nowrap">Totals</th>
</tr>
<xsl:for-each select="//Row[generate-id() = generate-id(key('casebystate', #StoreState)[1])]">
<xsl:sort select="#StoreState"/>
<xsl:for-each select="key('casebystate', #StoreState)">
<xsl:call-template name="CaseByState.rowview" />
</xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="CaseByState.rowview">
<xsl:variable name="cbs_NewSortValue" select="ddwrt:NameChanged(string(#StoreState), 0)"/>
<xsl:if test="string-length($cbs_NewSortValue) > 0">
<tr id="group0{generate-id()}">
<td class="abh-chrtStatTitle">
<xsl:value-of select="#StoreState"/>
</td>
<td class="abh-chrtStatValue">
<xsl:value-of select="count(key('casebystate', #StoreState))"></xsl:value-of>
</td>
</tr>
</xsl:if>
</xsl:template>
Thanks for the help in advance!
If you are using Sharepoint Designer 2010, are you able to select your dvwp in the designer, go to the data view tools section and click on paging from the options tab of the ribbon? That should allow you to specify paging for your webpart.

XSLT: Split Ungrouped Data into chunks

I need to turn
<question>
<static><![CDATA[Static Data]]></static>
<debit-row />
<debit-row />
<credit-row />
<header><![CDATA[Header HTML 1]]></header>
<debit-row />
<debit-row />
<credit-row />
</question>
into
<p>Static Data</p>
<ul>
<li>
<table>
<tr><td> debit row </td></tr>
<tr><td> credit row </td></tr>
<tr><td> credit row </td></tr>
</table>
</li>
<li> Header HTML 1
<table>
<tr><td> debit row </td></tr>
<tr><td> debit row </td></tr>
<tr><td> credit row </td></tr>
</table>
</li>
</ul>
Essentially, either a header or a debit-row indicates the start of a new chunk. Each chunk is a list item. Each set or rows is a table (as a rule, credit rows always come last so it's easy to tell when to start the table).
XSLT and XPATH seem very difficult and I'm having a very hard time looking up anything that I want to do at all, so if anyone has an excellent reference, I would appreciate that too.
I've started out with this xsl:
<xsl:template match="question">
<xsl:apply-templates select="static|header|debit-row[preceding-sibling::*[1] != header]" />
</xsl:template>
This is not a good start, because the templates are not applied to any debit-row at all, but they should be applied to the very first debit-row (it does not have a header element preceding it). Is that expression wrong?
Even if I get that to work, I need to find a way to say "Open a <ul> if this is the very first header or debit-row," and I'm not sure how to do that when applying the header/debit-row template. debit-row each has its own xml to be applied too (it needs a table row and td). I also have to open and close the table appropriately before the first debit-row and after the last credit-row.
I would seriously appreciate any help as I am stuck even getting the simple xpath expression above to work correctly.
I. XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing"
match="*[not(self::static or self::header)]"
use="generate-id(preceding-sibling::*
[self::static
or
self::header
][1]
)"/>
<xsl:template match="/*[static]">
<p><xsl:value-of select="static"/></p>
<ul>
<xsl:apply-templates select="static|header"/>
</ul>
</xsl:template>
<xsl:template match="static|header">
<li>
<xsl:value-of select=
"concat(self::header, '
')"/>
<table>
<xsl:apply-templates
select="key('kFollowing', generate-id())"/>
</table>
</li>
</xsl:template>
<xsl:template match=
"*/*[not(self::static or self::header)]">
<tr>
<td>
<xsl:value-of select=
"translate(name(),'-', ' ')"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<question>
<static><![CDATA[Static Data]]></static>
<debit-row />
<debit-row />
<credit-row />
<header><![CDATA[Header HTML 1]]></header>
<debit-row />
<debit-row />
<credit-row />
</question>
produces the wanted, correct result:
<p>Static Data</p>
<ul>
<li>
<table>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>credit row</td>
</tr>
</table>
</li>
<li>Header HTML 1
<table>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>credit row</td>
</tr>
</table>
</li>
</ul>
Explanation: Positional grouping using a key to define all elements that belong to a group.
II. XSLT 2.0 solution:
<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="/*[static]">
<p><xsl:value-of select="static"/></p>
<ul>
<xsl:for-each-group select="*"
group-starting-with="static|header">
<li>
<xsl:value-of separator="
" select=
"current-group()[1][self::header], ''"/>
<table>
<xsl:apply-templates
select="current-group()[position() gt 1]"/>
</table>
</li>
</xsl:for-each-group>
</ul>
</xsl:template>
<xsl:template match=
"*/*[not(self::static or self::header)]">
<tr>
<td>
<xsl:value-of select=
"translate(name(),'-', ' ')"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
when this XSLT 2.0 transformation is applied to the same XML document (above), again the same, correct result is produced:
<p>Static Data</p>
<ul>
<li>
<table>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>credit row</td>
</tr>
</table>
</li>
<li>Header HTML 1
<table>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>debit row</td>
</tr>
<tr>
<td>credit row</td>
</tr>
</table>
</li>
</ul>
Explanation: Using the XSLT 2.0 <xsl:for-each-group> instruction with a group-starting-with attribute. Also using the standard XSLT 2.0 function current-group().