Wrox Programmer Forums
Go Back   Wrox Programmer Forums > XML > XSLT
|
XSLT General questions and answers about XSLT. For issues strictly specific to the book XSLT 1.1 Programmers Reference, please post to that forum instead.
Welcome to the p2p.wrox.com Forums.

You are currently viewing the XSLT section of the Wrox Programmer to Programmer discussions. This is a community of software programmers and website developers including Wrox book authors and readers. New member registration was closed in 2019. New posts were shut off and the site was archived into this static format as of October 1, 2020. If you require technical support for a Wrox book please contact http://hub.wiley.com
 
Old July 14th, 2004, 10:23 AM
Authorized User
 
Join Date: Jul 2004
Posts: 29
Thanks: 0
Thanked 0 Times in 0 Posts
Default Problem with loop logic

Here is the XML document I have:

<Collection>
    <Item>
        <Name>Henry</Name>
    </Item>
    <Item>
        <Name>Henry</Name>
    </Item>
    <Item>
        <Name>Colin</Name>
    </Item>
    <Item>
        <Name>Henry</Name>
    </Item>
    <Item>
        <Name>Monty</Name>
    </Item>
</Collection>

If I want to check that no "Item" has a matching "Name" to another "Item" I could use the following code:

<xsl:for-each select="Collection/Item">
    <xsl:variable name="sName" select="Name" />
    <xsl:if test="count(/Collection/Item/Name[.=$sName]) > 1">
        <xsl:call-template
            name="DisplayError"><xsl:with-param name="ErrNum">570</xsl:with-param>
        </xsl:call-template>
    </xsl:if>
</xsl:for-each>

The problem with this is that I get the error message three times. What I really want to do is just detect the presence of duplicates and display the error once, something like this:

<xsl:for-each select="Collection/Item">
    <xsl:variable name="dupeFound" select="false()" />
    <xsl:variable name="sName" select="Name" />
    <xsl:if test="count(/Collection/Item/Name[.=$sName]) > 1">
        <xsl:variable name="dupeFound" select="true()" />
    </xsl:if>
    <xsl:if test="$dupeFound">
        <xsl:call-template
            name="DisplayError"><xsl:with-param name="ErrNum">570</xsl:with-param>
        </xsl:call-template>
    </xsl:if>
</xsl:for-each>

Only that doesn't work because you cannot re-assign a value to $dupeFound, and the use of it in the loop is scoped to the for-each loop.

Does anybody know how I can acheive what I need?

thanks in advance

holdmykidney
 
Old July 14th, 2004, 12:16 PM
Authorized User
 
Join Date: Jul 2004
Posts: 11
Thanks: 0
Thanked 0 Times in 0 Posts
Send a message via AIM to NotesSensei Send a message via Yahoo to NotesSensei
Default

Hi HoldmyKidney,
returning your brain-cpu time spent for me... :-)
Speak after me: "I will use looping in XSLT only as last resort, long live the set theory".
To overcome your problem you need to check result-sets rather than looping. It is quite a stretch getting started with XLST when you come from a programming background (I feel the pain daily ;)).

So try this:
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <xsl:apply-templates/>
    </xsl:template>
    <xsl:template match="Collection">
        <Results>
            <xsl:apply-templates/>
        </Results>
    </xsl:template>
    <xsl:template match="Item">
        <xsl:variable name="prevNames" select="count(preceding-sibling::*[Name=current()/Name])"/>
        <xsl:variable name="nextNames" select="count(following-sibling::*[Name=current()/Name])"/>
        <xsl:if test="$nextNames>0 and $prevNames=0">
            <Error>
Duplicate Entry found: <xsl:value-of select="Name"/>
            </Error>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

... it might be slow on BIG datasets....
;) stw

If you think education is expensive - try ignorance!
 
Old July 15th, 2004, 03:29 AM
Authorized User
 
Join Date: Jul 2004
Posts: 29
Thanks: 0
Thanked 0 Times in 0 Posts
Default

Cheers NotesSensei, that works *mostly* sweet.
However I probably didnt use very good example data.

Supposed this is the *modified* XML:

<Collection>
    <Item>
        <Name>Henry</Name>
    </Item>
    <Item>
        <Name>Henry</Name>
    </Item>
    <Item>
        <Name>Colin</Name>
    </Item>
    <Item>
        <Name>Henry</Name>
    </Item>
    <Item>
        <Name>Monty</Name>
    </Item>
    <Item>
        <Name>Colin</Name>
    </Item>
</Collection>

I now have two different instances of duplicates. You way is much better than mine, as I now get only one error message per instance of a duplicate, however I will still get multiple error messages with this kind of document.

What I really need is some way of simply detecting the presence of duplicates and reporting that, rather than reporting each instance.

What would be nice is some kind of group() or unique() function, but I don't suppose there is one.
 
Old July 15th, 2004, 03:50 AM
Authorized User
 
Join Date: Jul 2004
Posts: 53
Thanks: 0
Thanked 0 Times in 0 Posts
Default

Hi,

How about this for a different solution.
Basically build a string of occurences of each value, i.e "11123".
Then check the length of the string after removing all 1's.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:key name="item-name" match="Item" use="Name" />

    <xsl:template match="Collection">


        <xsl:variable name="dups">
            <xsl:for-each select="Item">
                <xsl:value-of select="count(key('item-name', Name))" />
             </xsl:for-each>
        </xsl:variable>


        <xsl:if test="string-length(translate($dups, '1', ''))!=0">
            There are duplicates
        </xsl:if>

    </xsl:template>

</xsl:stylesheet>


 
Old July 15th, 2004, 03:51 AM
Authorized User
 
Join Date: Jul 2004
Posts: 11
Thanks: 0
Thanked 0 Times in 0 Posts
Send a message via AIM to NotesSensei Send a message via Yahoo to NotesSensei
Default

Hi Holdymykidney,
that was kind of a miss understanding. I thought you need an error message for each unique error. To get just a single error message you have some options:
1) Remove the <Error> tag. Then you have a <Result> that is either empty (no duplicates) or has content (name of the duplicates)
2) Alter the processing tags so you process only the first and that has an apply template for following-sibling[1] inside the inverted if (if 0 then apply template)
Got the idea?
;) stw

If you think education is expensive - try ignorance!
 
Old July 16th, 2004, 07:11 AM
Authorized User
 
Join Date: Jul 2004
Posts: 53
Thanks: 0
Thanked 0 Times in 0 Posts
Default

Even easier:

1. Using axis:
<xsl:template match="Collection">
    <xsl:if test="Item[Name=preceding-sibling::Item/Name]">
       There are duplicates
    </xsl:if>
</xsl:template>

or

2. Using key (better for large docs)

<xsl:key name="item-name" match="Item" use="Name" />
...
<xsl:template match="Collection">
  <xsl:if test="Item[generate-id()=generate-id(key('item-name', Name)[2])]">
    There are duplicates
  </xsl:if>
</xsl:template>








Similar Threads
Thread Thread Starter Forum Replies Last Post
Looping through a list using logic tags problem aadz5 Struts 3 September 23rd, 2011 03:18 AM
problem while using <logic:iterate> tag be_sandip Struts 4 April 26th, 2007 11:27 PM
problem while using <logic:iterate> tag be_sandip BOOK: Professional Jakarta Struts 0 November 13th, 2006 08:05 AM
Login logic problem Gizmo_Gadgethead BOOK: ASP.NET 2.0 Website Programming Problem Design Solution ISBN: 978-0-7645-8464-0 0 September 1st, 2006 06:32 PM
Mixing Data access logic and business logic polrtex BOOK: Professional Jakarta Struts 0 December 15th, 2003 07:19 PM





Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
Copyright (c) 2020 John Wiley & Sons, Inc.