[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message]

Re: Removing duplicates for unknown number of elements

Subject: Re: Removing duplicates for unknown number of elements
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Wed, 14 Mar 2001 09:18:31 +0000
xsl for each row number
Hi Chris,

> 1. The first problem I have is that (from the XML input) you can see
> that after WS/CONTENT/ROWS/ROW, the nodes beneath that change .. is
> this bad XML design?

Well, you might want to think about using namespaces to divide the
stuff that's about the layout from the stuff that's about the content.
So you could have:

<ROW>
   <sport:NAME>Shaq O'Neal</sport:NAME>
   <sport:EMAIL>shaq@xxxxxxxxxx</sport:EMAIL>
</ROW>
...
<ROW>
   <sport:SPORT>Basketball</sport:SPORT>
   <sport:TEAM>Lakers</sport:TEAM>
   <sport:CITY>Los Angeles</sport:CITY>
</ROW>

It's not clear to me what the CONTENT, ROWS and ROW elements mean
particularly.  For example, why don't you have something that's purely
about the information that you want to hold, like:

<SPORT>Basketball</SPORT>
<TEAM>Lakers</TEAM>
<CITY>Los Angeles</CITY>
<PLAYERS>
   <PLAYER>
      <NAME>Shaq O'Neal</NAME>
      <EMAIL>shaq@xxxxxxxxxx</EMAIL>
   </PLAYER>
   ...
</PLAYERS>

> 2. Most of the stuff above works... it's just *bad-looking*... i got
> node() calls hooked up which don't look very nice...

Yes, many of your paths look very strange :)

For example:

  <xsl:value-of select="../node()/../TITLE"/>

This says: from the current node (a TYPE attribute) go up to the
parent (which will be a CONTENT element), then down to all its
children, then up again to their parent (the same CONTENT element) and
then get the value of its TITLE element child.  So you're oscillating
around needlessly.  Instead, just use:

  <xsl:value-of select="../TITLE" />

Then you have:
  
  <xsl:for-each select="../node()/ROW/node()">

This says: from the current node (a TYPE attribute) go up to the
parent (the CONTENT element), then get all its children, then get all
their ROW element children (there will only be any if the node from
the last step was a ROWS element), then down to all its children.
Actually you just want the ROW element's element children.  I think
you'd be better off with:

  <xsl:for-each select="../ROWS/ROW/*">

Similarly further down, you have:

  <xsl:for-each select="../node()/ROW">
     <tr bgcolor="#ffffff">
        <xsl:for-each select="node()">
           <td><xsl:value-of select="node()"/></td>
        </xsl:for-each>
     </tr>
  </xsl:for-each>

Which I think would be clearer as:

  <xsl:for-each select="../ROWS/ROW">
     <tr bgcolor="#ffffff">
        <xsl:for-each select="*">
           <td><xsl:value-of select="."/></td>
        </xsl:for-each>
     </tr>
  </xsl:for-each>

You *can* use node() all the time, especially if you've made sure
you're not counting whitespace-only text nodes by stripping them with:

  <xsl:strip-space elements="*" />

It might be slightly more efficient because the processor doesn't have
to worry about testing to see whether the node is an element or
whether it has a particular name, but it's probably less efficient in
the long run because you end up collecting nodes that you don't want.
It's also a lot harder to read, I think you'll agree.

Oh, and the other thing is that you could avoid all those '..' to go
up to the CONTENT element if the top-most xsl:for-each iterated over
the CONTENT elements themselves rather than their TYPE attributes:

  <xsl:for-each select="CONTENT[@TYPE = 'DB']">

And you can skip the xsl:if that's wrapped around that xsl:for-each -
it will only be processed anyway if it finds some elements to iterate
on.

> 3. I got most of the stuff to work on this page except for 1
> thing... on the XSL stylesheet, right after the <!---- ***** ---->
> comment tag, I get duplicate <th></th> tags generated because the
> loop I have there has no concept of uniqueness... so for the first
> chunk, I get:
>
> <tr><th>NAME</th><th>EMAIL</th><th>NAME</th><th>EMAIL</th><th>NAME</th><th>E
> MAIL</th></tr>
>
> instead of:
> <tr><th>NAME</th><th>EMAIL</th></tr> (which is my desired output).

Probably the easiest thing to do is to only get the children of the
first of the ROW elements, and use their names, so use:

   <xsl:for-each select="../ROWS/ROW[1]/*">
      <th><xsl:value-of select="name()"/></th>
   </xsl:for-each>

>From your sample, the rest of the ROW elements have exactly the same
children, so you only need look at the first one.  If you do have
different substructures for the ROW elements within a particular
CONTENT, then you probably want:

   <xsl:for-each select="../ROWS/ROW/*">
      <xsl:if test="not(../preceding-sibling::ROW/*
                           [name() = name(current())])">
         <th><xsl:value-of select="name()"/></th>
      </xsl:if>
   </xsl:for-each>

This iterates over each of the element children of the ROW elements.
It tests whether there are any element children of preceding ROW
elements that have the same name() as the current node.  If there
aren't, then it gives the heading cell.

I hope that helps,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread

PURCHASE STYLUS STUDIO ONLINE TODAY!

Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced!

Buy Stylus Studio Now

Download The World's Best XML IDE!

Accelerate XML development with our award-winning XML IDE - Download a free trial today!

Don't miss another message! Subscribe to this list today.
Email
First Name
Last Name
Company
Subscribe in XML format
RSS 2.0
Atom 0.3
Site Map | Privacy Policy | Terms of Use | Trademarks
Free Stylus Studio XML Training:
W3C Member
Stylus Studio® and DataDirect XQuery ™are products from DataDirect Technologies, is a registered trademark of Progress Software Corporation, in the U.S. and other countries. © 2004-2013 All Rights Reserved.