I'm trying to build a commenting system with XSLT. Here's the XML input for comments already submitted:
<in:inputs xmlns:in="http://www.composite.net/ns/transformation/input/1.0">
<!-- Input Parameter, XPath /in:inputs/in:param[#name='story_id'] -->
<in:param name="story_id">182485599</in:param>
<!-- Function Call Result (0 ms), XPath /in:inputs/in:result[#name='LoggedInWebUserID'] -->
<in:result name="LoggedInWebUserID">233459</in:result>
<!-- Function Call Result (9 ms), XPath /in:inputs/in:result[#name='XML_Comment']/root -->
<in:result name="XML_Comment">
<root xmlns="">
<Comments CommentID="1" ResponseCommentID="0" WebUserID="123456" FULL_NAME="Osikhuemhe Abulume" Comment="test comment!!!!" DateSubmitted="Feb 20 2013 1:34PM"/>
<Comments CommentID="2" ResponseCommentID="0" WebUserID="261337" FULL_NAME="Phillip Lowe" Comment="test comment2!!!!" DateSubmitted="Feb 20 2013 5:14PM"/>
<Comments CommentID="3" ResponseCommentID="1" WebUserID="000007" FULL_NAME="Norman Abbott" Comment="my response" DateSubmitted="Feb 20 2013 5:14PM"/>
<Comments CommentID="4" ResponseCommentID="0" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="Not impressed..." DateSubmitted="Feb 20 2013 4:10PM"/>
<Comments CommentID="5" ResponseCommentID="0" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="blah blah blah. " DateSubmitted="Feb 20 2013 4:11PM"/>
<Comments CommentID="6" ResponseCommentID="0" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="dfsfs" DateSubmitted="Feb 20 2013 4:14PM"/>
<Comments CommentID="7" ResponseCommentID="5" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="this is a response to blah blah blah." DateSubmitted="Feb 20 2013 4:52PM"/>
<Comments CommentID="8" ResponseCommentID="3" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="I don't agree with Norman. Terrible response." DateSubmitted="Feb 20 2013 5:39PM"/>
<Comments CommentID="9" ResponseCommentID="4" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="I'm impressed." DateSubmitted="Feb 20 2013 5:43PM"/>
<Comments CommentID="10" ResponseCommentID="1" WebUserID="233459" FULL_NAME="Tamara Failor" Comment="I've got something to say!" DateSubmitted="Feb 20 2013 6:34PM"/>
</root>
</in:result>
This should work like any other commenting system for news stories (see: http://www.npr.org/2013/02/20/172384724/when-a-bad-economy-means-working-forever)
That is - new comments (ResponseCommentID = 0) would always be pushed to the left.
Responses to these comments would sit indented underneath. (i don't care about indentions right now. I would like to get replies to comments to fall underneath each other)
There are two parts where I'm stuck. The first part is how each post should be called:
<xsl:variable name="story_id" select="/in:inputs/in:param[#name='story_id']" />
<xsl:variable name="root" select="/in:inputs/in:result[#name='XML_Comment']/root" />
<xsl:for-each select="$root/Comments[#ResponseCommentID=0]">
<!-- call the template to plot the first comment down -->
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:value-of select="#CommentID" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:value-of select="#ResponseCommentID" /></xsl:with-param>
</xsl:call-template>
<!-- if the comment has any responses, put those underneath the root -->
<xsl:for-each select="$root/Comments[current()/#CommentID = $root/Comments/#ResponseCommentID]">
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:value-of select="#CommentID" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:value-of select="#ResponseCommentID" /></xsl:with-param>
</xsl:call-template>
</xsl:for-each>
The (also very wrong) ending recursion part of the template:
<xsl:if test="#CommentID = $root/Comments/#ResponseCommentID">
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:attribute name="value"><xsl:value-of select="#CommentID[#CommentID = $root/Comments/#ResponseCommentID]" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:attribute name="value"><xsl:value-of select="#ResponseCommentID[#CommentID = $root/Comments/#ResponseCommentID]" /></xsl:with-param>
</xsl:call-template>
</xsl:if>
If anyone could push me in the right direction I'd very much appreciate it. if more info is needed let me know. The actual template "thecomment" is simply taking the comment passed to it and formatting it the way I'd like.
Rather than using an xsl:for-each and then a call to a named template, you could consider combining the two into one xsl:apply-templates call. Firstly, you would select the comments with a ResponseCommentID attribute of 0.
<xsl:apply-templates
select="in:inputs/in:result[#name='XML_Comment']/root/Comments[#ResponseCommentID='0']" />
Then, you would have a template to match the Comments attribute, where you would output the comment details. You could then recursively get the response comments like so:
<xsl:apply-templates select="../Comments[#ResponseCommentID = current()/#CommentID]" />
This would just recursively call the same Comments template until there are no more reponses.
Here is the full XSLT in this case (I am outputting the comments as list items in HTML just as an example)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:in="http://www.composite.net/ns/transformation/input/1.0" exclude-result-prefixes="in">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<ul>
<xsl:apply-templates select="in:inputs/in:result[#name='XML_Comment']/root/Comments[#ResponseCommentID='0']" />
</ul>
</xsl:template>
<xsl:template match="Comments">
<li>
<xsl:value-of select="#Comment" />
<xsl:if test="../Comments[#ResponseCommentID = current()/#CommentID]">
<ul>
<xsl:apply-templates select="../Comments[#ResponseCommentID = current()/#CommentID]" />
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
This gives the following output
<ul>
<li>test comment!!!!
<ul>
<li>my response
<ul>
<li>I don't agree with Norman. Terrible response.</li>
</ul>
</li>
<li>I've got something to say!</li>
</ul>
</li>
<li>test comment2!!!!</li>
<li>Not impressed...
<ul>
<li>I'm impressed.</li>
</ul>
</li>
<li>blah blah blah.
<ul>
<li>this is a response to blah blah blah.</li>
</ul>
</li>
<li>dfsfs</li>
</ul>
However, it would be more efficient to use an xsl:key here to look up the responses to comments:
<xsl:key name="Comments" match="Comments" use="#ResponseCommentID" />
Then to get the top-level comments you would do this:
<xsl:apply-templates select="key('Comments', '0')" />
And to get the responses to a given comment in your matching template, you would do this
<xsl:apply-templates select="key('Comments', #CommentID)" />
The following XSLT also gives the same results
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:in="http://www.composite.net/ns/transformation/input/1.0" exclude-result-prefixes="in">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="Comments" match="Comments" use="#ResponseCommentID" />
<xsl:template match="/">
<ul>
<xsl:apply-templates select="key('Comments', '0')" />
</ul>
</xsl:template>
<xsl:template match="Comments">
<li>
<xsl:value-of select="#Comment" />
<xsl:if test="key('Comments', #CommentID)">
<ul>
<xsl:apply-templates select="key('Comments', #CommentID)" />
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
I think I figured it out. Here's what my XLST looks like now. It will go thru all comments, but only if it's the first post (#ResponseCommentID = 0).
<xsl:for-each select="$root/Comments">
<xsl:if test="#ResponseCommentID = 0 and #CommentID != $root/Comments/#ResponseCommentID">
<!-- call the template to first plot the comment down -->
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:value-of select="#CommentID" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:value-of select="#ResponseCommentID" /></xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
This is the recursive part at the end. It looks at all the comments, and calls the template for each #ResponseCommentID attribute that is equal to the current #CommentID.
<xsl:for-each select="$root/Comments[#ResponseCommentID = current()/#CommentID]">
<xsl:call-template name="thecomment">
<xsl:with-param name="CommentID"><xsl:value-of select="#CommentID" /></xsl:with-param>
<xsl:with-param name="ResponseCommentID"><xsl:value-of select="#ResponseCommentID" /></xsl:with-param>
</xsl:call-template>
</xsl:for-each>
Still don't understand it fully (I keep having to replay the sequence of events in my head), but I believe this works. :)
Related
Using XSLT 1.0 with an RSS calendar feed, I want to exclude expired items - those with pubDates before the current date - then include only three current items. The result should be the next three future events. I used http://exslt.org/date/index.html to create a variable for the current system date. The problem is that when I select="item[not(position() > 4)] and substring(item/pubDate,5,11) >= $current", I end up with less than three items if any of the first ones are expired. Apparently my code selects three items then removes the expired ones, which is not what I want. Is it possible to save a copy of the unexpired items and then select three of them?
Since XSLT 1.0 doesn't provide inequality string comparison operators, I may not be able to see if a value such as "30 Oct 2013" is greater than "29 Oct 2013," I can format the values as 30102013 and 29102013 instead, but it still seems that I'm trying to concatenate channel/item/pubDate before I have selected it. So knowing how to process the XML/RSS in two stages, if possible, would be helpful.
I have tried several techniques, with similar results:
<xsl:for-each select="substring(item/pubDate,5,11) >= $current and item[not(position() > 4)]">
<xsl:template match="item[not(position() > 4)]">
<xsl:apply-templates select="item"/>
<xsl:for-each select="substring(item/pubDate,5,11) >= $current">
<xsl:if test=" item[not(position() > 4)]">
Sample XML:
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="http://calendar.example.edu/" rel="self" type="application/rss+xml"/>
<title>University Calendar - Featured Events</title>
<link>http://calendar.example.edu/</link>
<description>List of featured events on calendar.example.edu</description>
<language>en-us</language>
<pubDate>Tue, 27 Oct 2013 20:47:05 CDT</pubDate>
<item>
<title>Creative Movement Program Student Show</title>
<description/>
<link>http://calendar.example.edu/?&y=2013&m=10&d=30&eventdatetime_id=19721</link>
<guid>calendar.example.edu/?&y=2013&m=10&d=30&eventdatetime_id=19721</guid>
<pubDate>Wed 30 Oct 2013, 17:00:00 CDT</pubDate>
</item>
<item>
<title>Philosophy Career Fair</title>
<description>The Department of Philosophy brings recruiters from around the state to interview seniors and alumni.</description>
<link>http://calendar.example.edu/?&y=2013&m=11&d=04&eventdatetime_id=16427</link>
<guid>calendar.example.edu/?&y=2013&m=11&d=04&eventdatetime_id=16427</guid>
<pubDate>Mon 04 Nov 2013, 07:00:00 CDT</pubDate>
</item>
<item>
<title>Football vs. Caltech</title>
<description/>
<link>http://calendar.example.edu/?&y=2013&m=12&d=07&eventdatetime_id=16521</link>
<guid>calendar.example.edu/?&y=2013&m=12&d=07&eventdatetime_id=16521</guid>
<pubDate>Sat 07 Dec 2013, 00:00:00 CDT</pubDate>
</item>
<item>
<title>Mural Exhibition</title>
<description>The College of Arts presents an overview of wall paintings from the Caves of Lascaux to the Kiev train station.</description>
<link>http://calendar.example.edu/?&y=2014&m=01&d=14&eventdatetime_id=16759</link>
<guid>calendar.example.edu/?&y=2014&m=01&d=14&eventdatetime_id=16759</guid>
<pubDate>Tue 14 Jan 2014, 07:00:00 CDT</pubDate>
</item>
</channel>
</rss>
Current XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" extension-element-prefixes="date" version="1.0" xmlns:date="http://exslt.org/dates-and-times" >
<xsl:template name="lf">
<xsl:text/>
</xsl:template>
<xsl:template match="rss">
<section id="campusEvents" role="region">
<h2 id="eventsTitle">
Campus Events
</h2>
<xsl:apply-templates select="channel"/>
<div class="moreLink">
Full Calendar
</div>
</section>
</xsl:template>
<xsl:template match="channel">
<xsl:variable name="currDay" select="substring(date:date(),9,2)"/>
<xsl:variable name="currMonth">
<xsl:call-template name="format-month">
<xsl:with-param name="date" select="date:date()"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="currYear" select="substring(date:date(),1,4)"/>
<xsl:variable name="current" select="concat($currDay,' ',$currMonth,' ',$currYear )"/>
<xsl:for-each select="item[not(position() > 4)] and substring(item/pubDate,5,11) >= $current">
<div class="eventBlock">
<xsl:call-template name="lf"/>
<div class="dateBlock">
<xsl:call-template name="lf"/>
<div class="eventMonth">
<xsl:value-of select="substring(pubDate,8,3)"/>
</div>
<div class="eventDate">
<xsl:value-of select="substring(pubDate,5,2)"/>
</div>
</div>
<xsl:call-template name="lf"/>
<div class="eventDescription">
<a class="url" href="{link}">
<xsl:value-of select="title"/>
</a>
<xsl:call-template name="lf"/>
</div>
<xsl:call-template name="lf"/>
</div>
<xsl:call-template name="lf"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="format-month">
<xsl:param name="date"/>
<xsl:variable name="monthName" select="substring(date:date(),6,2)"/>
<xsl:variable name="month">
<xsl:choose>
<xsl:when test="$monthName = '01'">Jan</xsl:when>
<xsl:when test="$monthName = '02'">Feb</xsl:when>
<xsl:when test="$monthName = '03'">Mar</xsl:when>
<xsl:when test="$monthName = '04'">Apr</xsl:when>
<xsl:when test="$monthName = '05'">May</xsl:when>
<xsl:when test="$monthName = '06'">Jun</xsl:when>
<xsl:when test="$monthName = '07'">Jul</xsl:when>
<xsl:when test="$monthName = '08'">Aug</xsl:when>
<xsl:when test="$monthName = '09'">Sep</xsl:when>
<xsl:when test="$monthName = '10'">Oct</xsl:when>
<xsl:when test="$monthName = '11'">Nov</xsl:when>
<xsl:when test="$monthName = '12'">Dec</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$month"/>
</xsl:template>
</xsl:stylesheet>
Desired Result (after Oct. 30 event has expired):
<section role="region" id="campusEvents">
<h2 id="eventsTitle">
Campus Events
</h2>
<div class="eventBlock">
<div class="dateBlock">
<div class="eventMonth">Nov</div>
<div class="eventDate">04</div>
</div>
<div class="eventDescription">
Philosophy Career Fair
</div>
</div>
<div class="eventBlock">
<div class="dateBlock">
<div class="eventMonth">Dec</div>
<div class="eventDate">07</div>
</div>
<div class="eventDescription">
Football vs. Caltech
</div>
</div>
<div class="eventBlock">
<div class="dateBlock">
<div class="eventMonth">Jan</div>
<div class="eventDate">14</div>
</div>
<div class="eventDescription">
Mural Exhibition
</div>
</div>
<div class="moreLink">
Full Calendar
</div>
</section>
If you want to compare dates then you'll have to somehow massage the various date expressions into all-numeric yyyymmdd format (e.g. 20131029) so that the chronological ordering is equivalent to numerical ordering. For the current date that's a simple global variable:
<xsl:variable name="curDateStr" select="date:date()" />
<xsl:variable name="currentDateNum"
select="concat(substring($curDateStr, 1, 4),
substring($curDateStr, 6, 2),
substring($curDateStr, 9, 2))" />
and to parse the pubDate values I'd use a named template that's the reverse of your current format-month
<xsl:template name="parse-date">
<xsl:param name="dateStr" />
<xsl:value-of select="substring($dateStr, 12, 4)" /><!-- year -->
<xsl:variable name="month" select="substring($dateStr, 8, 3)" />
<xsl:choose>
<xsl:when test="$month = 'Jan'">01</xsl:when>
<xsl:when test="$month = 'Feb'">02</xsl:when>
<!-- etc. -->
</xsl:choose>
<xsl:value-of select="substring($dateStr, 5, 2)" /><!-- day -->
</xsl:template>
Now the main logic can be implemented using a tail-recursive template which is the nearest you can get in XSLT to a "while" loop:
<xsl:template match="item">
<xsl:param name="numItems" select="3" />
<xsl:if test="$numItems > 0"><!-- stop if we hit the threshold -->
<xsl:variable name="itemDate">
<xsl:call-template name="parse-date">
<xsl:with-param name="dateStr" select="pubDate" />
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$itemDate > $currentDateNum">
<!-- do what you need to do to produce output for this item -->
<!-- ..... -->
<xsl:apply-templates select="following-sibling::item[1]">
<!-- we processed this item, so decrement $numItems -->
<xsl:with-param name="numItems" select="$numItems - 1" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="following-sibling::item[1]">
<!-- we ignored this item, so don't decrement $numItems -->
<xsl:with-param name="numItems" select="$numItems" />
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
<xsl:if>
</xsl:template>
and then in the channel template you start this "loop" by applying templates to the first item only
<xsl:template match="channel">
<xsl:apply-templates select="item[1]" />
</xsl:template>
The item template will then keep processing siblings until either it runs out of item elements completely, or it has processed 3 items that meet the date criteria.
I realize that you are really good at programming and that your answers are dependable.
Is it possible for you to assist me in resolving an issue I am having with my xslt code? I'm new to progamming so I would appreciate any assistance I can get.
Your solution in grouping 3 divs in a row is found at the link below, but I do not know how to apply it to my code. I am using Sitecore and I have a div block that corresponds to each page generated to produce metro-like blocks, 3 in a row. So far I generates the desired divs but does not put them three in a row. My code is found below.
XSLT How can I wrap each 3 elements by div?
I would appreciate any help I can get.
<?xml version="1.0" encoding="UTF-8"?>
<!--=============================================================
File: ServicesFeatureblocks.xslt
Created by: sitecore\admin
Created: 3/27/2013 11:50:57 AM
Copyright notice at bottom of file
==============================================================-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sc="http://www.sitecore.net/sc"
xmlns:dot="http://www.sitecore.net/dot"
exclude-result-prefixes="dot sc">
<!-- output directives -->
<xsl:output method="html" indent="no" encoding="UTF-8" />
<!-- parameters -->
<xsl:param name="lang" select="'en'"/>
<xsl:param name="id" select="''"/>
<xsl:param name="sc_item"/>
<xsl:param name="sc_currentitem"/>
<!-- variables -->
<!-- Uncomment one of the following lines if you need a "home" variable in you code -->
<xsl:variable name="Services" select="sc:item('/sitecore/content/home/Services',.)" />
<!--<xsl:variable name="home" select="/*/item[#key='content']/item[#key='home']" />-->
<!--<xsl:variable name="home" select="$sc_currentitem/ancestor-or-self::item[#template='site root']" />-->
<!-- entry point -->
<xsl:template match="*">
<xsl:apply-templates select="$sc_item" mode="main"/>
</xsl:template>
<!--==============================================================-->
<!-- main -->
<!--==============================================================-->
<xsl:variable name="group" select="3" />
<xsl:template match="/">
<xsl:apply-templates select="$sc_currentitem[position() mod $group = 1]" />
</xsl:template>
<xsl:template match="item" mode="inner">
<div class="block orange">
<xsl:value-of select="sc:fld('Title',.)" />
</div>
<item/>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:apply-templates
select="./item|following-sibling::services/item[position() < $group]" mode="inner" />
</div>
</xsl:template>
</xsl:stylesheet>
If you can use a sublayout instead of xslt rendering, You can achieve your functionality easily using Listview with GroupTemplate
<asp:ListView ID="listview1" runat="server" GroupItemCount="3" GroupPlaceholderID="groupPlaceholder"
ItemPlaceholderID="itemPlaceholder">
<LayoutTemplate>
<div class="productsContent">
<asp:PlaceHolder ID="groupPlaceholder" runat="server"></asp:PlaceHolder>
</div>
</LayoutTemplate>
<GroupTemplate>
<div class="product-rows">
<asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
</div>
</GroupTemplate>
<ItemTemplate>
<div class="products">
</div>
</ItemTemplate>
</asp:ListView>
I have been training myself in XSLT for about 1.5 months. I have made a simplified shorter version of what I am having trouble figuring out, and would highly appreciate any help at all as I am stuck on the issue. Thanks!
Basic Situation:
There's a string in a root attribute with an ancestor of element definition
xpath:
/v3:QualityMeasureDocument/v3:component/v3:dataCriteriaSection/v3:definition/v3:valueSet/v3:id/#root
...that when matched with the id from a valueSet attribute with an ancestor of element entry, xpath:
/v3:QualityMeasureDocument/v3:component/v3:dataCriteriaSection/v3:entry/v3:observationCriteria/v3:value/#valueSet
or
/v3:QualityMeasureDocument/v3:component/v3:dataCriteriaSection/v3:entry/v3:observationCriteria/v3:code/#valueSet
the output (needs to and currently does) display the string, along with its required attribute(s).
However, when there is no match for the string in these locations, the string must also be listed, with a 'Not Specified' header.
THE ERROR:
The 'Not Specified' header and its string are being listed even when all of the existing strings match. In this scenario, there should only be matched strings listed.
The Problem Translator (XSL file) :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan"
xmlns:v3="urn:hl7-org:v3"
xmlns:rvs="urn:ihe:iti:svs:2008">
<xsl:output method="html" standalone="yes" omit-xml-declaration="no" indent="yes" xalan:indent-amount="2"/>
<xsl:template
match="/v3:QualityMeasureDocument">
<html>
<head>
<title>Test 'I'</title>
</head>
<body>
<ul>
<xsl:apply-templates select="//v3:dataCriteriaSection" />
</ul>
</body>
</html>
</xsl:template>
<xsl:template
match="v3:dataCriteriaSection">
<xsl:for-each select="//v3:entry">
<xsl:if test="*/v3:value/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:value/#valueSet"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="*/v3:code/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:code/#valueSet"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="*/v3:participant/v3:roleParticipant/v3:code/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:participant/v3:roleParticipant/v3:code/#valueSet"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template
name="definitionValueSet">
<xsl:param name="cur_valueSetID"/>
<xsl:for-each select="//v3:valueSet">
<xsl:choose>
<xsl:when test="$cur_valueSetID != v3:id/#root or
not( v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] ) or
not( v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID )">
<xsl:if test="not($cur_valueSetID = '')">
<li>
<xsl:text>Not Specified</xsl:text>
<ul>
<li>
<xsl:text>ValueSet: </xsl:text>
<xsl:value-of select="$cur_valueSetID"></xsl:value-of>
</li>
</ul>
</li>
</xsl:if>
</xsl:when>
<xsl:when test="v3:id/#root = $cur_valueSetID">
<xsl:if test="v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)]">
<xsl:if test="v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID">
<li>
<xsl:text>Id: </xsl:text>
<xsl:value-of select="v3:id/#root"/>
<xsl:for-each select="v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/rvs:ConceptList/rvs:Concept">
<ul>
<li>
<xsl:if test="not(#code = '')">
<xsl:if test="#code">
<xsl:text>code = </xsl:text>
<xsl:value-of select="#code"></xsl:value-of>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:if>
</li>
</ul>
</xsl:for-each>
</li>
</xsl:if>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input file 'I' xml :
<?xml version="1.0" encoding="UTF-8"?>
<QualityMeasureDocument xmlns="urn:hl7-org:v3">
<component>
<dataCriteriaSection>
<definition>
<valueSet>
<!-- Value Set for Race -->
<id root='1.1.1.1.1.1.1' />
<text>
<reference
value='https://localhost/RetrieveValueSet?id=1.1.1.1.1.1.1' />
<RetrieveValueSetResponse xmlns="urn:ihe:iti:svs:2008">
<ValueSet id="1.1.1.1.1.1.1">
<ConceptList>
<Concept code="4" />
<Concept code="5" />
<Concept code="6" />
</ConceptList>
</ValueSet>
</RetrieveValueSetResponse>
</text>
</valueSet>
</definition>
<definition>
<valueSet>
<id root='1.1.1.1.1.1.2' />
<text>
<reference
value='https://localhost/RetrieveValueSet?id=1.1.1.1.1.1.2' />
<RetrieveValueSetResponse xmlns="urn:ihe:iti:svs:2008">
<ValueSet id="1.1.1.1.1.1.2">
<ConceptList>
<Concept code="007.2" />
<Concept code="007.3" />
</ConceptList>
</ValueSet>
</RetrieveValueSetResponse>
</text>
</valueSet>
</definition>
<entry>
<observationCriteria>
<code code="424144002" codeSystem="123123213"
displayName="FEWFW" />
<value>
<low />
<high />
</value>
</observationCriteria>
</entry>
<entry>
<observationCriteria>
<code code="DFHKJ" codeSystem="ASKJDHK" displayName="ASDNJK" />
<value>
<width />
</value>
</observationCriteria>
</entry>
<entry>
<observationCriteria>
<code code="FDSFD" codeSystem="JHBHJB" displayName="HJGJH" />
<value valueSet="1.1.1.1.1.1.1" />
</observationCriteria>
</entry>
<entry>
<encounterCriteria>
<code valueSet="1.1.1.1.1.1.2" />
</encounterCriteria>
</entry>
</dataCriteriaSection>
</component>
</QualityMeasureDocument>
Expected Output Code 'I' HTML :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns:xalan="http://xml.apache.org/xalan" xmlns:rvs="urn:ihe:iti:svs:2008" xmlns:v3="urn:hl7-org:v3">
<head>
<title>Test 'I'</title>
</head>
<body>
<ul>
<li>Id: 1.1.1.1.1.1.1
<ul>
<li>code = 4 </li>
</ul>
<ul>
<li>code = 5 </li>
</ul>
<ul>
<li>code = 6 </li>
</ul>
</li>
<li>Id: 1.1.1.1.1.1.2
<ul>
<li>code = 007.2 </li>
</ul>
<ul>
<li>code = 007.3 </li>
</ul>
</li>
</ul>
</body>
</html>
It would be simple to create the correct output through a hack, or just erasing code, but this needs to work in all situations. Such as, having no matches at all and displaying only the 'Not Specified' header and its string each time it occurs, or a mixture of both situations. The code currently works in a situation where there are no matches, and displays the 'Not Specified' header and its string each time it occurs.
It seems like if this could be done, "if it's not a match AND hasn't already been listed" it would solve the problem.
Hope that helps. If you would like more information or more files let me know. Any tips at all would be great! Thanks.
I think the main problem you are having is within your named template "definitionValueSet", where you loop over valueSet elements with an xsl:for-each
<xsl:for-each select="//v3:valueSet">
Within this you test whether the relevant attributes match your current parameter, and if you find a match, you output your list. But all valueSet elements will be searched, so even if one matches, you are still going to be processing the ones that don't and so your xsl:if for a non-match is called too. This results in the "Not Specified" being output.
What you really need to be doing is matching only the valueSet element if it exists, and if none exist that match, output your "Not Specified".
One way to do this is to first create a variable that holds the unique id of the valueSet element that matches
<xsl:variable name="valueSet">
<xsl:value-of select="generate-id(//v3:valueSet[v3:id/#root = $cur_valueSetID and v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] and v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID])"/>
</xsl:variable>
Then, you can test whether this has been set, and if so, apply the template for that element. If not set, you can have your not-specified code
<xsl:choose>
<xsl:when test="$valueSet">
<xsl:apply-templates select="//v3:valueSet[generate-id() = $valueSet]"/>
</xsl:when>
<xsl:otherwise>
<!-- Not Specified -->
</xsl:otherwise>
</xsl:choose>
Here is the full XSLT, which should hopefully give the output you require
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xalan" xmlns:v3="urn:hl7-org:v3" xmlns:rvs="urn:ihe:iti:svs:2008">
<xsl:output method="html" standalone="yes" omit-xml-declaration="no" indent="yes" xalan:indent-amount="2"/>
<xsl:template match="/v3:QualityMeasureDocument">
<html>
<head>
<title>Test 'I'</title>
</head>
<body>
<ul>
<xsl:apply-templates select="//v3:dataCriteriaSection"/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="v3:dataCriteriaSection">
<xsl:for-each select=".//v3:entry">
<xsl:if test="*/v3:value/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:value/#valueSet"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="*/v3:code/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:code/#valueSet"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="*/v3:participant/v3:roleParticipant/v3:code/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:participant/v3:roleParticipant/v3:code/#valueSet"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="definitionValueSet">
<xsl:param name="cur_valueSetID"/>
<xsl:variable name="valueSet">
<xsl:value-of select="generate-id(//v3:valueSet[v3:id/#root = $cur_valueSetID and v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] and v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID])"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$valueSet != ''">
<xsl:apply-templates select="//v3:valueSet[generate-id() = $valueSet]"/>
</xsl:when>
<xsl:otherwise>
<li>
<xsl:text>Not Specified</xsl:text>
<ul>
<li>
<xsl:text>ValueSet: </xsl:text>
<xsl:value-of select="$cur_valueSetID"/>
</li>
</ul>
</li>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="v3:valueSet">
<li>
<xsl:text>Id: </xsl:text>
<xsl:value-of select="v3:id/#root"/>
<xsl:for-each select="v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/rvs:ConceptList/rvs:Concept">
<ul>
<li>
<xsl:if test="not(#code = '')">
<xsl:if test="#code">
<xsl:text>code = </xsl:text>
<xsl:value-of select="#code"/>
<xsl:text/>
</xsl:if>
</xsl:if>
</li>
</ul>
</xsl:for-each>
</li>
</xsl:template>
</xsl:stylesheet>
Do note, as mentioned in a comment, be careful with the use of expression //v3:entry. This is an absolute expression, and will match any element at all in the document (i.e it searches from the root element downwards). If you wanted only to look under the current element, use the relative expression .//v3:entry
EDIT: For a slight variant on this, as a potentially more efficient way to look up the valueSet elements you could define a key at the top of the XSLT document
<xsl:key name="valueSet" match="v3:valueSet" use="generate-id()" />
Then, instead of doing this to look up the correct valueSet element
<xsl:apply-templates select="//v3:valueSet[generate-id() = $valueSet]"/>
You could use the key instead
<xsl:apply-templates select="key('valueSet', $valueSet)"/>
The following is a simple-ish alternate solution (does not use generate-id() or key() ) provided for comprehension. It is likely less efficient than and should not replace Tim C's excellent answer. I am simply providing this so people can learn, and to show that I put full effort into solving this issue, instead of just getting what I needed and moving on.
The solution is a fix to the original 'Problem Translator'. The only section that needs to be edited (although heavily) from that XSL file is the "definitionValueSet" named template.
First we need to create a variable $valueSetData which stores all of the values of v3:id/#root in a one time pass, with a ',' in between them, in order to be individually referenced later. It's like a new temporary database.
The for-each contains a predicate which limits the matches as per requirements. These are included within the declaration because they are local nodes there. Also, this keeps from processing extra conditional checks later.
<xsl:variable name="valueSetData">
<xsl:for-each select="//v3:valueSet[
v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] and
v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID]">
<xsl:value-of select="v3:id/#root"/>
<xsl:text>,</xsl:text>
</xsl:for-each>
</xsl:variable>
Next we have the choose statement, edited for specifics. The statement determines if there is a match to the $cur_valueSetID within the $valueSetData 'database'.
The for-each predicate limits duplicate matches (with the wrong values in addition due to context).
<xsl:choose>
<xsl:when test="contains($valueSetData, $cur_valueSetID)">
<xsl:for-each select="//v3:valueSet[$cur_valueSetID = v3:id/#root]">
<!-- Display found matches which meet all requirements -->
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<!-- Display Not Specified -->
</xsl:otherwise>
</xsl:choose>
The full "definitionValueSet" template:
<xsl:template
name="definitionValueSet">
<xsl:param name="cur_valueSetID"/>
<xsl:variable name="valueSetData">
<xsl:for-each select="//v3:valueSet[
v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] and
v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID]">
<xsl:value-of select="v3:id/#root"/>
<xsl:text>,</xsl:text>
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($valueSetData, $cur_valueSetID)">
<xsl:for-each select="//v3:valueSet[$cur_valueSetID = v3:id/#root]">
<li>
<xsl:text>Id: </xsl:text>
<xsl:value-of select="v3:id/#root"/>
<xsl:for-each select="v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/rvs:ConceptList/rvs:Concept">
<ul>
<li>
<xsl:if test="not(#code = '')">
<xsl:if test="#code">
<xsl:text>code = </xsl:text>
<xsl:value-of select="#code"></xsl:value-of>
<xsl:text/>
</xsl:if>
</xsl:if>
</li>
</ul>
</xsl:for-each>
</li>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<li>
<xsl:text>Not Specified</xsl:text>
<ul>
<li>
<xsl:text>ValueSet: </xsl:text>
<xsl:value-of select="$cur_valueSetID"></xsl:value-of>
</li>
</ul>
</li>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I am working on XSLT.
Source XML:
<?xml version="1.0" encoding="iso-8859-1"?>
<Content>
<alertHeader>
<ol xmlns="http://www.w3.org/1999/xhtml">
<li>
<strong>Review</strong>
your current available balance. It can be obtained 24 hours a day, 7 days a week through
Account Activity
any 123 ATM or by calling
<a id="dynamicvariable" href="#" name="Customercare">[Customercare]</a>
at
<a id="dynamicvariable" href="#" name="contactNo">[contactNo]</a>
.
</li>
<li>
<strong>Take into consideration</strong>
<ul>
<li>Please get in touch with us</li>
<li>Please consider this as important info</li>
</ul>
</li>
<li>
<strong>Current</strong>
Statementt doesnot match the requirement
<a id="dynamicvariable" href="#" name="Actual acccount">[Actual acccount]</a>
,plus
<a id="dynamicvariable" href="#" name="totalcharges">[totalcharges]</a>
Make u r response as positive.
</li>
</ol>
<p xmlns="http://www.w3.org/1999/xhtml"></p>
<div xmlns="http://www.w3.org/1999/xshtml"></div>
<span xmlns="http://www.w3.org/1999/xshtml"></span>
</alertHeader>
</Content>
I want to write a XSLT to pass entire content in the tag alertHeader as value to another template.
I want to modify this code as follows.
1.Remove the tags <p></p>, and <div></div>,<span></span> and <a></a>. I want to remove only tags but not the value of the tags. It should be there as it is.
2.Pass the content including tags to "Process" template.
Output required:
<aaa>
<text>
<ol xmlns="http://www.w3.org/1999/xhtml">
<li>
<strong>Review</strong>
your current available balance. It can be obtained 24 hours a day, 7 days a week through
<dynamicvariable name="Customercare"/>
at
<dynamicvariable name="contactNo"/>
.
</li>
<li>
<strong>Take into consideration</strong>
<ul>
<li>Please get in touch with us</li>
<li>Please consider this as important info</li>
</ul>
</li>
<li>
<strong>Current</strong>
Statementt doesnot match the requirement
</li>
</ol>
</text>
</aaa>
current XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="alertHeader">
<xsl:call-template name="process">
<xsl:with-param name="text" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="process">
<xsl:param name="text" />
<xsl:variable name="head" select="substring-before($text, '[')" />
<xsl:variable name="tag" select="substring-before(substring-after($text, '['), ']')" />
<xsl:variable name="tail" select="substring-after($text, ']')" />
<xsl:choose>
<xsl:when test="$head != '' and $tag != ''">
<xsl:value-of select="$head" />
<dynamicVariable name="{$tag}" />
<!-- recursive step: process the remainder of the string -->
<xsl:call-template name="process">
<xsl:with-param name="text" select="$tail" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Can any one say what all the changes required for my code.
Thanks.
There is no need to do any initial processing of the XML document and then pass it to the process template. From looking at your question, it looks like you have a requirement (which you haven't explicitly mentioned) to transform 'tags' in the text, such as form '[Total_Fee]', to dynamicVariable elements.
So, what you need to do is firstly just have a template to ignore your chosen nodes, but continue matching the elements and text within them.
<xsl:template match="Content|alertHeader|xhtml:p|xshtml:div|xshtml:span|xhtml:a">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
Do note the complication here is that you have defined different namespaces for some of your nodes (and these would have to be declared in the XSLT document). If you have multiple namespaces, you could also do the following.
<xsl:template match="Content|alertHeader|*[local-name() = 'p']|*[local-name() = 'span']|*[local-name() = 'div']|*[local-name() = 'a']">
Without namespaces you could do the following
<xsl:template match="Content|alertHeader|p|div|span|a">
Next, your named process template can then be combined to match text() elements
<xsl:template match="text()" name="process">
This allows it to match text elements, and recursively call itself to look for tags within the text.
Here is the full XSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xshtml="http://www.w3.org/1999/xshtml"
exclude-result-prefixes="xhtml xshtml">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Content|alertHeader|xhtml:p|xshtml:div|xshtml:span|xhtml:a">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
<xsl:template match="text()" name="process">
<xsl:param name="text" select="." />
<xsl:choose>
<xsl:when test="contains($text, ']') and contains($text, '[')">
<xsl:value-of select="substring-before($text, '[')"/>
<dynamicVariable name="{substring-before(substring-after($text, '['), ']')}"/>
<xsl:call-template name="process">
<xsl:with-param name="text" select="substring-after($text, ']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<ol xmlns="http://www.w3.org/1999/xhtml">
<li>
<strong>Review</strong> your current available balance. It can be obtained 24 hours a day, 7 days a week through Account Activity any Wells Fargo ATM or by calling <dynamicVariable name="Call_Center_Name" xmlns="" /> at <dynamicVariable name="Phone_Number" xmlns="" /> . </li>
<li>
<strong>Take into account</strong>
<ul>
<li>Your pending transactions and any additional transactions that have not yet been deducted from your available balance, such as checks you have written or upcoming scheduled automatic payments.</li>
<li>Any transactions that have been returned because you did not have enough money in your account at that time; they may be resubmitted for payment by the person or party who received the payment from you</li>
</ul>
</li>
<li>
<strong>Deposit</strong> enough money to establish and maintain a positive account balance. A deposit of at least <dynamicVariable name="Absolute_Available_Balance" xmlns="" /> ,plus <dynamicVariable name="Total_Fee" xmlns="" /> in fees, would have been required to make your account balance positive at the time we sent this notice. </li>
</ol>
The problem in xslt-1.0 is that you can't access a result node or nodeset, as in there is no XPath or function or anything to refer to the transformation result of a template.
Is it essential that you call process as it is? The way I'd go about this is to make process a template that transforms exactly one node, and then call it from every node below alertHeader, like:
<xsl:template match="alertHeader">
<!-- switch modes so we know we need to call process -->
<xsl:apply-templates mode="callproc"/>
</xsl:template>
<xsl:template match="*" mode="callproc">
<!-- call process on THIS node -->
<xsl:call-template name="process">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
<!-- descend -->
<xsl:apply-templates mode="callproc"/>
</xsl:template>
<!-- all the stuff that needs stripping -->
<xsl:template match="p|div|span|a" mode="callproc">
<!-- call process on the text() portion -->
<xsl:call-template name="process">
<xsl:with-param name="text" select="text()"/>
</xsl:call-template>
<!-- no descent here -->
</xsl:template>
Update:
Without process template
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:xsh="http://www.w3.org/1999/xshtml">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Content">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="alertHeader">
<xsl:element name="aaa">
<xsl:element name="text">
<!-- switch modes so we know we need to call process -->
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<!-- descend -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<!-- all the stuff that needs stripping -->
<xsl:template match="xh:p|xh:div|xh:span|xsh:div|xsh:span">
<xsl:apply-templates/>
</xsl:template>
<!-- was process -->
<xsl:template match="xh:a[#name]">
<xsl:element name="dynamicvariable" namespace="http://www.w3.org/1999/xhtml">
<xsl:attribute name="name">
<xsl:value-of select="#name"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="xh:a"/>
<xsl:template match="text()">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Output:
<ol xmlns="http://www.w3.org/1999/xhtml">
<li>
<strong>Review</strong>
your current available balance. It can be obtained 24 hours a day, 7 days a week through
any Wells Fargo ATM or by calling
<dynamicvariable name="Call_Center_Name"/>
at
<dynamicvariable name="Phone_Number"/>
.
</li>
<li>
<strong>Take into account</strong>
<ul>
<li>Your pending transactions and any additional transactions that have not yet been deducted from your available balance, such as checks you have written or upcoming scheduled automatic payments.</li>
<li>Any transactions that have been returned because you did not have enough money in your account at that time; they may be resubmitted for payment by the person or party who received the payment from you</li>
</ul>
</li>
<li>
<strong>Deposit</strong>
enough money to establish and maintain a positive account balance. A deposit of at least
<dynamicvariable name="Absolute_Available_Balance"/>
,plus
<dynamicvariable name="Total_Fee"/>
in fees, would have been required to make your account balance positive at the time we sent this notice.
</li>
</ol>
</text>
</aaa>
I want to do something similar to the following:
<xsl:for-each select="Item">
<xsl:if test="postion()=1 or position()=7>
<ul>
</xsl:if>
<li>An Item</li>
<xsl:if test="position()=2">
</ul>
</xsl>
</xsl:for-each>
</ul>
This will not work however because xslt views the unclosed <ul> within the if statement as invalid.
Sample input:
<Item>1</Item>
<Item>2</Item>
<Item>3</Item>
<Item>4</Item>
<Item>5</Item>
<Item>6</Item>
<Item>7</Item>
<Item>8</Item>
<Item>9</Item>
<Item>10</Item>
Expected output:
<ul>
<li>An Item<li>
<li>An Item<li>
<li>An Item<li>
<li>An Item<li>
<li>An Item<li>
<li>An Item<li>
</ul>
<ul>
<li>An Item<li>
<li>An Item<li>
<li>An Item<li>
<li>An Item<li>
</ul>
Thanks
-Ben
You should rewrite it to avoid the need for such tag soup. First group the items, such that each group is transformed to its own UL in the output, and then iterate over those groups (and for each group, iterate over the items within in).
For the particular code that you've posted, with hardcoded values, it's trivially refactored by moving the output for Item into its own template (a good idea, anyway), and then reusing that:
<xsl:template match="Item">
<li>An Item</li>
</xsl:template>
...
<ul>
<xsl:apply-templates select="Item[1]"/>
</ul>
<ul>
<xsl:apply-templates select="Item[position() ≥ 2 and position() ≤ 7]"/>
</ul>
<xsl:apply-templates select="Item[position() > 7]"/>
In practice, though, you probably has some more complicated way of determining the boundaries of your groups, but without knowing the exact requirement, it's hard to be more specific when answering this. You'll likely want to look at the Muenchian method of grouping if you use some key. If your groups are all fixed-size, with a few hardcoded exceptions (e.g. first item in its own group, and then every next 10 items form a new group), you could iterate over position instead.
If want you want is simply to group items into groups of six (or fewer, at the end) then you can use a recursive call to a template that spits out the first six Items in a list and then calls itself for any left over.
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="no" encoding="ISO-8859-1"/>
<xsl:template match="Item">
<li>Item</li>
</xsl:template>
<xsl:template name="group-of-six">
<xsl:param name="items"/>
<ul>
<xsl:for-each select="$items[position() < 7]">
<xsl:apply-templates select="."/>
</xsl:for-each>
</ul>
<xsl:if test="count($items) > 6">
<xsl:call-template name="group-of-six">
<xsl:with-param name="items" select="$items[position() > 6]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/">
<Lists>
<xsl:call-template name="group-of-six">
<xsl:with-param name="items" select="//Item"/>
</xsl:call-template>
</Lists>
</xsl:template>
</xsl:transform>