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 June 27th, 2012, 11:52 AM
Authorized User
 
Join Date: Feb 2012
Posts: 13
Thanks: 4
Thanked 0 Times in 0 Posts
Default Checking for unique nodes

Good morning,
I am using xslt 1.0 and need to parse the embedded xml file and only pick up lines where the yearIndicator,originalID, and studentSSN are unique with the greatest transactionNumber.
I was using the following but didnt have much luck:
<code>
<xsl:variable name="studentSSN" select="flat:field[@name = 'studentSSN']"/>
<xsl:variable name="yearIndicator" select="./flat:datum[@name = 'yearIndicator']"/>
<xsl:variable name="originalID" select="./flat:datum[@name = 'originalID']"/>
<xsl:variable name="transactionNumber" select="./flat:datum[@name = 'transactionNumber']"/>
<xsl:if test="not(preceding::flat:datum[text() = $yearIndicator]) and not(preceding::flat:datum[text() = $studentSSN]) and not(preceding::flat:datum[text() = $originalID])" >
<xsl:apply-templates select="node()|@*"/>
</xsl:if>
</code>
A sample xml file is as follows:
<code>
<flat:datum type="record">
<flat:field name="yearIndicator">2</flat:field>
<flat:field name="studentSSN">550951236</flat:field>
<flat:field name="originalID">RA</flat:field>
<flat:field name="transactionNumber">02</flat:field>
</flat:datum>
<flat:datum type="record">
<flat:field name="yearIndicator">2</flat:field>
<flat:field name="studentSSN">550951236</flat:field>
<flat:field name="originalID">RA</flat:field>
<flat:field name="transactionNumber">03</flat:field>
</flat:datum>
</code>
Hence in the above example, I would need to pick up the second record since the transactionNumber is greater and ignore the first.
 
Old June 27th, 2012, 12:06 PM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

I would group with Muenchian grouping and then sort in descending order e.g.
Code:
<xsl:key name="k1" match="flat:datum[@type = 'record']"
  use="concat(flat:field[@name = 'yearIndicator'], '|', flat:field[@name = 'studentSSN'], '|', flat:field[@name = 'originalID'])"/>

<xsl:template match="/">
  <xsl:apply-templates select="//flat:datum[@type = 'record'][generate-id() = generate-id(key('k1', concat(flat:field[@name = 'yearIndicator'], '|', flat:field[@name = 'studentSSN'], '|', flat:field[@name = 'originalID']))[1])"/>
</xsl:template>

<xsl:template match="flat:datum[@type = 'record']">
  <xsl:for-each select="key('k1', concat(flat:field[@name = 'yearIndicator'], '|', flat:field[@name = 'studentSSN'], '|', flat:field[@name = 'originalID']))">
    <xsl:sort select="flat:field[@name = 'transactionNumber']" data-type="number" order="descending"/>
    <xsl:if test="position() = 1">
       <xsl:copy-of select="."/>
    </xsl:if>
  </xsl:for-each>
</xsl:template>
Untested but should give you an idea.
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog

Last edited by Martin Honnen; June 27th, 2012 at 12:09 PM.. Reason: adding key call argument I forgot
The Following User Says Thank You to Martin Honnen For This Useful Post:
madowl (June 27th, 2012)
 
Old June 27th, 2012, 12:19 PM
Authorized User
 
Join Date: Feb 2012
Posts: 13
Thanks: 4
Thanked 0 Times in 0 Posts
Default

Thank you very much Martin. This seems to work great (was missing a ] at the end)- sorry I wasnt familiar with this method. I appreciate your quick response. However, I am noticing that that there are certain elements before these datum tags that are not being copied over. I guess I would need to copy the whole template over?
 
Old June 27th, 2012, 12:42 PM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

You didn't really say what you want to do with other elements, so the sample I posted simply extracts those unique elements with the highest transactionNumber. You have now posted a very long input sample but you haven't said or described what result you want. I am not sure what you want to do with other elements. Consider to post minimal but representative samples of input and desired output. And to allow us to read code samples please use http://p2p.wrox.com/misc.php?do=bbcode#code
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
 
Old June 27th, 2012, 12:56 PM
Authorized User
 
Join Date: Feb 2012
Posts: 13
Thanks: 4
Thanked 0 Times in 0 Posts
Default

Sorry about that Martin. What you did was exactly what I was looking for with regards to sorting..however, the other elements would just need to be copied over..hence the sample output file - after your processing would be similar to:
Code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vocado version="1.25.7-SNAPSHOT_20238-120614.1030" xsi:schemaLocation="http://vocado.vsm-systems.com/xsd/vocado-tx vocado-tx.xsd
http://vocado.vsm-systems.com/xsd/flat-plugin flat-plugin.xsd
http://vocado.vsm-systems.com/xsd/xslt-plugin xslt-plugin.xsd
http://vocado.vsm-systems.com/xsd/fop-plugin fop-plugin.xsd
http://vocado.vsm-systems.com/xsd/csv-plugin csv-plugin.xsd
" xmlns="http://vocado.vsm-systems.com/xsd/vocado-tx" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslt="http://vocado.vsm-systems.com/xsd/xslt-plugin" xmlns:flat="http://vocado.vsm-systems.com/xsd/flat-plugin" xmlns:csv="http://vocado.vsm-systems.com/xsd/csv-plugin" xmlns:fop="http://vocado.vsm-systems.com/xsd/fop-plugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <definitions>
        <entities>
            <entity name="isir">
                <field type="text" label="Original Social Security Number" name="studentSsn" global="true">
                    <key global="true" name="ssn"/>
                </field>
                <field type="number" label="Year indicator" name="yearIndicator" global="true">
                    <key global="true" name="yearIndicator"/>
                </field>
                <field type="text" label="Original Name ID" name="originalNameId" global="true">
                    <key global="true" name="originalNameId"/>
                </field>
                <field type="number" label="The CPS transaction number of this ISIR." name="transactionNumber" global="true">
                    <key global="true" name="transactionNumber"/>
                </field>
            </entity>
        </entities>
        <scopes>
            <scope global="true" datum-type="isir" name="ISIR.scope">
                <field datum-type="isir" datum-field="yearIndicator" xpath="isir/yearIndicator" name="yearIndicator"/>
            </scope>
        </scopes>
        <flat:entities>
            <entity name="record">
                <field type="text" name="yearIndicator"/>
                <field type="text" name="studentSSN"/>
                <field type="text" name="originalID"/>
                <field type="text" name="transactionNumber"/>
</entity>
        </flat:entities>
    </definitions>
<flat:data>
        <flat:datum type="record">
            <flat:field name="yearIndicator">2</flat:field>
            <flat:field name="studentSSN">550951236</flat:field>
            <flat:field name="originalID">RA</flat:field>
            <flat:field name="transactionNumber">03</flat:field>
</flat:datum>
    </flat:data>
</vocado>
 
Old June 27th, 2012, 01:06 PM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

I think you want the identity transformation plus code for the 'flat:data' element containing those record elements to find the unique ones with the greatest transaction number so
Code:
<xsl:key name="k1" match="flat:datum[@type = 'record']"
  use="concat(flat:field[@name = 'yearIndicator'], '|', flat:field[@name = 'studentSSN'], '|', flat:field[@name = 'originalID'])"/>


<xsl:template match="@* | node()">
  <xsl:copy>
     <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="flat:data">
  <xsl:copy>
    <xsl:apply-templates select="flat:datum[@type = 'record'][generate-id() = generate-id(key('k1', concat(flat:field[@name = 'yearIndicator'], '|', flat:field[@name = 'studentSSN'], '|', flat:field[@name = 'originalID']))[1])]"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="flat:datum[@type = 'record']">
  <xsl:for-each select="key('k1', concat(flat:field[@name = 'yearIndicator'], '|', flat:field[@name = 'studentSSN'], '|', flat:field[@name = 'originalID']))">
    <xsl:sort select="flat:field[@name = 'transactionNumber']" data-type="number" order="descending"/>
    <xsl:if test="position() = 1">
       <xsl:copy-of select="."/>
    </xsl:if>
  </xsl:for-each>
</xsl:template>
Again untested so might contain syntax errors like missing matching brackets but should give you an idea.
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
The Following User Says Thank You to Martin Honnen For This Useful Post:
madowl (June 27th, 2012)
 
Old June 27th, 2012, 01:17 PM
Authorized User
 
Join Date: Feb 2012
Posts: 13
Thanks: 4
Thanked 0 Times in 0 Posts
Default

Thank you very very much Martin!!! This seems to work very well. I appreciate the quick response and great guidance.
 
Old June 27th, 2012, 01:22 PM
Authorized User
 
Join Date: Feb 2012
Posts: 13
Thanks: 4
Thanked 0 Times in 0 Posts
Default

A quick question Martin, what does the "1" at the end of the generate-id signify?
Code:
<xsl:apply-templates select="flat:datum[@type = 'record'][generate-id() = generate-id(key('isirKey', concat(flat:field[@name = 'yearIndicator'], '|', flat:field[@name = 'studentSSN'], '|', flat:field[@name = 'originalID']))[1])]"/>
 
Old June 27th, 2012, 01:31 PM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

It's a positional predicate applied on the result of the key function (not the generate-id) so with
Code:
key('key-name', keyExpression)[1]
we ensure that the first node in document order found by the key function is used to generate the id from. The generate-id might do that automatically I think but for efficiency depending on the XSLT processor the [1] might help. Read more about Muenchian grouping at http://www.jenitennison.com/xslt/grouping/muenchian.xml
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
 
Old June 27th, 2012, 01:35 PM
Authorized User
 
Join Date: Feb 2012
Posts: 13
Thanks: 4
Thanked 0 Times in 0 Posts
Default

Thanks again! much appreciated





Similar Threads
Thread Thread Starter Forum Replies Last Post
Unique nodes based on two attributes malim_sheikh XSLT 2 August 11th, 2009 07:38 AM
Display only unique Nodes kylektran XSLT 1 July 17th, 2009 08:41 AM
Is uniqueidentifier is always unique ? vinod_yadav1919 SQL Server 2000 4 May 30th, 2008 11:31 AM
duplicates and unique sheba0907 SQL Language 6 July 3rd, 2006 02:43 PM
Two unique Attribues shumba XML 5 February 28th, 2006 09:05 PM





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