Hi Vivek,
Vivek Shinde wrote:
Hi Folks,
Thanks a lot for all your responses. Michael Kay has provided an alternative of
using position() function. What I am trying to do is display the category
heading only once if the article of that category is found.
Thanks again.
Vivek
It's always interesting for me to see what questions stem mass response from the
big guns on the list. As sure as Christmas will come in December this is one of
the questions that will always generate a response from the likes of Michael
Kay, David Carlisle, Wendell Piez, etc... etc... etc... It seems Santa can
begin his travel preperations as, once again, the "global variable" question has
proven itself worthy of it's own spot on the master list of Cosmic Constant's
(right alongside the speed of light, Pi, and, oh yes, Christmas in December ;)
What you are attempting is an interesting problem to solve with XSLT 1.0 which
didnt include a lot of built-in string processing capabilities. As such parsing
strings and "flagging" the current instance of an element in a way to tell the
processor to handle this particular node different than the nodes following it
is not as straight forward as I know a lot of people would like it to be. There
is a way to solve your problem however... in fact there are two that I can think
of off the top of my head. The first is the least expensive of the two but
utilizes the node-set() functionality that, although almost every processor
inmplements some sort of support for, is not part of the 1.0 spec (a point that
has not gone overlooked since the release of the spec). The second is supported
in the 1.0 spec but is REALLY expensive and should be avoided at all costs for
any implementation other than very small XML source documents.
Solution 1 - By creating a temporary tree (method show below) you can create an
exact copy of the original elements of interest with one difference - adding a
new attribute that "flags" the elements that match the string test in a way that
will allow a simple check of an attribute during a second run through the now
copied elements to determine if the flag exists and if so if its the first
instance in the tree in which it does exist. Take a look at this code and it
should be pretty easy to see what I am referring to. (if it looks all mangled in
your email client you may want to view it with "wrap" turned off or set to a
higher number)
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common" version="1.0" exclude-result-prefixes="exslt">
<xsl:template match="/newsletter/content"><!-- Selects all "content"
elements. By default all of the "content" elements children will be selected as
well -->
<xsl:variable name="temp"><!-- creates a variable that will contain a copy
of the original "article" elements plus a new attribute that we can later test
for existence and determine if its the first of its kind -->
<xsl:apply-templates select="article" mode="temp"/><!-- selects all
"article" elements and then begins a recursive parse through each element and
finds the template that best matches the criteria set forth - in this case we
all "article" elements will match the template set to mode="temp". See this
template below for more information as to what happens within the template to
determine what gets copied to the Result Tree Fragement we are creating -->
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($temp)"
mode="writeContents"/><!-- selects the $temp tree we have created, converts it
to a node-set using the "exslt:node-set()" function, and then takes each
immediate child element and searches for the best matching template for further
processing. Notice: You need to remember to make a reference to the exslt
namespace in the stylesheet element. See above for example. To be able to use
this namespace you need a processor that supports it... Saxon was used in this
instance but there are others that support exslt by default as well as
extensions that can be refered to and used. See "http://www.exslt.org" for more
information or "http://www.Saxonica.com" for information about Saxon's
implementation -->
</xsl:template>
<xsl:template match="article" mode="temp"><!-- all "article" elements from
the temporary tree creation will match this template -->
<xsl:copy><!-- copies just the "article" element to the output tree -->
<xsl:if test="contains(metadata/article-classification, 'Biz-Webcast')">
<xsl:attribute name="passStringTest">true</xsl:attribute>
</xsl:if><!-- tests for the 'Biz-Webcast' string contained in the
"metadata/article-classification" element and if it is found creates a new
attribute for the current element in context (in this case "article") called
"passStringTest" and sets it value to "true". We could have used any value here
as later on all we are going to test for is the existince of the
"passStringTest" attribute as this is enough information to determine if this
element originally passed the string test -->
<xsl:copy-of select="* | @*"/><!-- copies all children and attributes
that are a part of the current element back into the current element. There are
no attributes in the sample XML I created but I dont know if that is true so I
showed the method to copy the attributes just in case in your source XML they do
exist -->
</xsl:copy>
</xsl:template>
<xsl:template match="article[(_at_)passStringTest][1]" mode="writeContents"><!--
This template will be used if and only if an attribute called "passStringTest"
exists AND this element is in the first position of all elements that pass the
attribute test -->
<hr/><!-- simple hard rule so we can see where each output stopes and
starts -->
<p><xsl:value-of select="'This is the first instance of an element that
passed the string test'"/></p><!-- this can of course be anything you want -->
<p><xsl:value-of select="metadata/article-classification"/></p><!-- As can
this -->
<hr/><!-- simple hard rule so we can see where each output stopes and
starts -->
</xsl:template>
<xsl:template match="article" mode="writeContents"><!-- all templates that
havent matched to an previous templates with attributes match="article" and
mode="writeContents" will match to this template to be further processed -->
<hr/><!-- simple hard rule so we can see where each output stopes and
starts -->
<p><xsl:value-of select="metadata/article-classification"/></p><!-- again,
this could be anything you want it to be -->
<hr/><!-- simple hard rule so we can see where each output stopes and
starts -->
</xsl:template>
</xsl:stylesheet>
Heres the sample XML I used:
<?xml version="1.0" encoding="UTF-8"?>
<newsletter>
<content>
<article>
<metadata>
<article-classification>This is some test text</article-classification>
</metadata>
</article>
<article>
<metadata>
<article-classification>This is some test text</article-classification>
</metadata>
</article>
<article>
<metadata>
<article-classification>This is some test text with the string
Biz-Webcast in it</article-classification>
</metadata>
</article>
<article>
<metadata>
<article-classification>This is some test text</article-classification>
</metadata>
</article>
<article>
<metadata>
<article-classification>This is some test text with the string
Biz-Webcast in it</article-classification>
</metadata>
</article>
<article>
<metadata>
<article-classification>This is some test text with the string
Biz-Webcast in it</article-classification>
</metadata>
</article>
</content>
</newsletter>
And then the output of the transformation:
<hr>
<p>This is some test text</p>
<hr>
<hr>
<p>This is some test text</p>
<hr>
<hr>
<p>This is the first instance of an element that passed the string test</p>
<p>This is some test text with the string Biz-Webcast in it</p>
<hr>
<hr>
<p>This is some test text</p>
<hr>
<hr>
<p>This is some test text with the string Biz-Webcast in it</p>
<hr>
<hr>
<p>This is some test text with the string Biz-Webcast in it</p>
<hr>
Solution 2 - The second solution requires utilization of the preceding-sibling
axis in XPath. The problem with using preceding-sibling in any sort of test is
as the processor continues through the tree another preceding-sibling is added
to the list of preceding-siblings. This test will evaluate each and every
preceding-sibling so it doesnt take long for performance to become a BIG issue.
Depending on the amount of text contained in each article-classification
element you could copy all of the preceding-siblings into one variable and test
that variable for existence of the test string. But if the text in this element
is fairly long and the XML tree big then you can imagine the size of the string
thats created by the time its get to the end once again bringing the processor
to its knees. Still, of the two I would probably take the variable string
concat test as, overall, there's a better chance of your performance being
somewhat manageable when doing one boolean test instead of 100. Here's what the
code would look like.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/newsletter/content/article">
<hr/>
<xsl:if test="contains(metadata/article-classification, 'Biz-Webcast')">
<xsl:variable name="checkString">
<xsl:for-each
select="preceding-sibling::article/metadata/article-classification">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:if test="not(contains($checkString, 'Biz-Webcast'))">
This is the first instance of the element
article/metadata/article-classification that contains the string 'Biz-Webcast'.
</xsl:if>
</xsl:if>
<xsl:value-of select="metadata/article-classification"/>
<hr/>
</xsl:template>
</xsl:stylesheet>
Which outputs:
<hr>This is some test text
<hr>
<hr>This is some test text
<hr>
<hr>
This is the first instance of the element
article/metadata/article-classification that contains the string 'Biz-Webcast'.
This is some test text with the string Biz-Webcast in it
<hr>
<hr>This is some test text
<hr>
<hr>This is some test text with the string Biz-Webcast in it
<hr>
<hr>This is some test text with the string Biz-Webcast in it
<hr>
When using the same source XML.
The # of lines of code using this solution is a lot less but I'll leave it to
you to decide if lines of code is more important than overall performance or if
using strict XSLT 1.0 without use of the node-set() function is something that
is important.
Best of luck to you!
<M:D/>
:: The Saxon.NET early beta has proven to be a great success! ::
:: Stay tuned for the announcment of the general public beta release soon ::
:: Visit http://www.x2x2x.org/x2x2x/home for more information ::
:: RSS & ATOM feeds -> http://www.x2x2x.org/x2x2x/home/xml_feeds/ ::