Wrox Programmer Forums

Need to download code?

View our list of code downloads.

Go Back   Wrox Programmer Forums > XML > XSLT
Password Reminder
Register
| FAQ | Members List | Calendar | Search | Today's Posts | Mark Forums Read
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 tens of thousands of software programmers and website developers including Wrox book authors and readers. As a guest, you can read any forum posting. By joining today you can post your own programming questions, respond to other developers’ questions, and eliminate the ads that are displayed to guests. Registration is fast, simple and absolutely free .
DRM-free e-books 300x50
Reply
 
Thread Tools Search this Thread Display Modes
  #1 (permalink)  
Old January 18th, 2006, 04:18 PM
Registered User
 
Join Date: Jan 2006
Location: Grimstad, , Norway.
Posts: 7
Thanks: 0
Thanked 0 Times in 0 Posts
Default Selecting elements up until a certain one

Hello,

I have a document which looks like this:
Code:
<table>
    <tr><th>Contact</th><td>Arthur</td></tr>
    <tr><th>Phone</th><td>555-LOGS</td></tr>
    <tr><th>Contact</th><td>Carrie</td></tr>
    <tr><th>Phone</th><td>+4732112330</td></tr>
    <tr><th>Email</th><td>c@rr.ie</td></tr>
    <tr><th>Contact</th><td>Doug</td></tr>
</table>
I would like to transform this into something like this:
Code:
<Contacts>
    <Contact>
        <Name>Arthur</Name>
        <Phone>555-LOGS</Phone>
    </Contact>
..
.and so on for each contact
</Contacts>
That means I have to select all the elements up until there's an element whitch contains a <th>Contact</th>

This my first try (messy)

Code:
<Contacts>
<xsl:for-each select='//h:table/h:tr/h:th[normalize-space(text())="Contact"]'>
    <Contact>

        <Name>
            <xsl:value-of select='./following-sibling::h:td'/>
        </Name>

        <xsl:for-each select='./parent::h:tr/following-sibling::h:tr/h:th[normalize-space(text())!="Contact"]/parent::h:tr/following-sibling::h:tr'>
            Print out contents of tr...
        </xsl:for-each>

    </Contact> 

</xsl:for-each>
</Contacts>
This kind of worked, but of course all <tr>s without the <th>Contact</th> in them were printed out, instead of just the ones up until the first <tr> with a <th>Contact</th> in it.

This is my second try. (even messier)
The idea is to set a variable TRUE when the parser gets to a <tr><th>Contact</th>.. skip printing the rest of the <tr>s if it has been set. I came up with that half-asleep, so it's not very elegant.

Code:
<Contacts>
<xsl:for-each select='//h:table/h:tr/h:th[normalize-space(text())="Contact"]'>
    <Contact>

        <Name>
            <xsl:value-of select='./following-sibling::h:td'/>
        </Name>

        <xsl:for-each select='./parent::h:tr/following-sibling::h:tr/h:th'>

            <xsl:variable name='trigger'>
                <xsl:if test='normalize-space(text())="Contact"'>
                    TRUE
                </xsl:if>
            </xsl:variable>

            <xsl:if test='not($trigger="TRUE")'>
                Print out contents of tr..
            </xsl:if>

        </xsl:for-each>

    </Contact> 

</xsl:for-each>
</Contacts>
..this doesn't really work.

Any ideas how I can do this the right way?
Reply With Quote
  #2 (permalink)  
Old January 18th, 2006, 06:50 PM
mhkay's Avatar
Wrox Author
Points: 18,481, Level: 59
Points: 18,481, Level: 59 Points: 18,481, Level: 59 Points: 18,481, Level: 59
Activity: 100%
Activity: 100% Activity: 100% Activity: 100%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,960
Thanks: 0
Thanked 292 Times in 287 Posts
Default

In XSLT 2.0 you can do this with

<xsl:for-each-group group-starting-with="tr[th='Contact']">

In 1.0 it's more difficult (as most things are). There are a number of approaches, one that's reasonably easy to understand is along the lines:

<xsl:template match="tr[th='Contact']">
  <Contact>
    <Name><xsl:value-of select="td"/></Name>
    <xsl:variable name="this" select="generate-id()"/>
    <xsl:apply-templates select="following-sibling::*[generate-id(preceding-sibling::Contact[1]) = $this)]"/>
  </Contact>

to put that in words: process all the following siblings of this Contact whose most recent preceding-sibling Contact is the element you started at.

Michael Kay
http://www.saxonica.com/
Author, XSLT Programmer's Reference and XPath 2.0 Programmer's Reference
Reply With Quote
  #3 (permalink)  
Old January 19th, 2006, 10:44 AM
Registered User
 
Join Date: Jan 2006
Location: Grimstad, , Norway.
Posts: 7
Thanks: 0
Thanked 0 Times in 0 Posts
Default

Thank you for your reply. I'm a little new to this..

I only have XSLT 1.0, so I made that template and gave it the name «Contacts», and then used call-template in the <Contacts>-element (which is in another template matching "/", and I can't have a template inside a template, right?).

This gives me one <Contact>-element, when there are several lines <tr><th>Contact</th><td>Arthur</td></tr> in the source. Why is that?

Also, the <xsl:apply-templates...-line, what template does this apply to its nodes? Itself, or do I have to make a template for those nodes?
Reply With Quote
  #4 (permalink)  
Old January 19th, 2006, 11:10 AM
Registered User
 
Join Date: Jan 2006
Location: Grimstad, , Norway.
Posts: 7
Thanks: 0
Thanked 0 Times in 0 Posts
Default

Okay, I figured out a couple of things once I realized call-template doesn't change the current node. It now gives me a <Contact>-element for each contact in the list, but I'm still unsure how to print the sub elements (<Phone> etc).

Reply With Quote
  #5 (permalink)  
Old January 19th, 2006, 12:11 PM
mhkay's Avatar
Wrox Author
Points: 18,481, Level: 59
Points: 18,481, Level: 59 Points: 18,481, Level: 59 Points: 18,481, Level: 59
Activity: 100%
Activity: 100% Activity: 100% Activity: 100%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,960
Thanks: 0
Thanked 292 Times in 287 Posts
Default

You could replace the <xsl:apply-templates select="following-siblings....." with <xsl:copy-of select="following-siblings....", and then it would just copy the elements to the output. But I assumed you didn't want to do that, you wanted to apply some processing to them (turning them into HTML). To do that, you just write a template rule for each kind of element that you want to process, for example

<xsl:template match="phone">
  <img src="phone-icon.gif"/>
  <xsl:value-of select="."/>
</xsl:template>

The thing about apply-templates is it allows you to separate two concerns. The apply-templates instruction is concerned with the decision whether and when to process a particular element. The template rule is concerned with how to process it, once it's selected. The reason I wrote the apply-templates part without the xsl:template part in my response was that your problem as stated was about how to select the elements for processing, not what to do with them once selected.

Michael Kay
http://www.saxonica.com/
Author, XSLT Programmer's Reference and XPath 2.0 Programmer's Reference
Reply With Quote
  #6 (permalink)  
Old January 19th, 2006, 01:22 PM
Registered User
 
Join Date: Jan 2006
Location: Grimstad, , Norway.
Posts: 7
Thanks: 0
Thanked 0 Times in 0 Posts
Default

Eureka! It works :)

Only problem is, it selects all the <tr>s, up until, and including, the next <tr><th>Contact... which is the start of the next <Contact>-element.

Code:
<xsl:template match='//h:tr[h:th[normalize-space(text())="Kontakt"]]'>
                    <Contact>

                        <Name><xsl:value-of select="h:td"/></Name>
                        <xsl:variable name="this" select="generate-id()"/>

                        <xsl:for-each select='./following-sibling::h:tr[generate-id(preceding-sibling::h:tr[h:th[normalize-space(text())="Kontakt"]][1]) = $this]'>

                            <xsl:element name="{./h:th}">
                                <xsl:value-of select="h:td"/>
                            </xsl:element>

                        </xsl:for-each>

                    </Contact>
</xsl:template>
Reply With Quote
Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off
Trackbacks are Off
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Selecting Elements at Runtime to Display using XSL pnviv XSLT 1 November 22nd, 2007 05:08 AM
Selecting multiple elements from the source XML mkansal XSLT 15 June 18th, 2007 09:40 AM
help in selecting elements spandit XSLT 1 April 9th, 2007 07:27 PM
Selecting unique set of elements Chamkaur XSLT 1 March 15th, 2007 05:43 AM
selecting certain xml elements fogofogo XML 2 December 5th, 2005 07:33 AM



All times are GMT -4. The time now is 06:05 AM.


Powered by vBulletin®
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.
© 2013 John Wiley & Sons, Inc.