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 January 18th, 2006, 04:18 PM
Registered User
 
Join Date: Jan 2006
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>[email protected]</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?
 
Old January 18th, 2006, 06:50 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
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
 
Old January 19th, 2006, 10:44 AM
Registered User
 
Join Date: Jan 2006
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?
 
Old January 19th, 2006, 11:10 AM
Registered User
 
Join Date: Jan 2006
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).

 
Old January 19th, 2006, 12:11 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
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
 
Old January 19th, 2006, 01:22 PM
Registered User
 
Join Date: Jan 2006
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>





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





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