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 April 9th, 2011, 04:10 PM
Registered User
 
Join Date: Apr 2011
Posts: 8
Thanks: 3
Thanked 0 Times in 0 Posts
Default some help wanted with grouping following siblings

Hi there,

I'm trying to convert an initial flat XML to a nested XML but got stuck on the final bits and pieces. I now have the following XML code :

<li><dl><dt>blabla</dt></dl></li>
<dd>blablabla</dd>
<dt>blablabla</dt>
.. much more <dd> and <dt> elements

<li><dl><dt>blabla2</dt></dl></li>
<dd>blablabla2</dd>
<dt>blablabla2</dt>
.. much more <dd> and <dt> elements
<p>some other element</>

And I would like to achieve the following

...
<li>
<dl>
<dt>blabla</dt>
<dd>blablabla</dd>
<dt>blablabla</dt>
.. much more <dd> and <dt> elements
</dl>
</li>
<li>
<dl>
<dt>blabla2</dt>
<dd>blablabla2</dd>
<dt>blablabla2</dt>
.. much more <dd> and <dt> elements
</dl>
</li>
<p>some other element</p>

Or in other words : Whenever I have a List element that contains a <dl> element I want to insert all the following <dt> and <dd> elements following on the <li> element up until the first element that's neither <dt> or <dd>

Anyone that could put me on the right track ? I'm using xslt 2.0 so I assume I need to use some combination of grouping and following sibling, but it's a bit too complex for me to comprehend. Thanks in advance !
Reply With Quote
  #2 (permalink)  
Old April 9th, 2011, 06:48 PM
mhkay's Avatar
Wrox Author
Points: 18,487, Level: 59
Points: 18,487, Level: 59 Points: 18,487, Level: 59 Points: 18,487, Level: 59
Activity: 100%
Activity: 100% Activity: 100% Activity: 100%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

Looks something like

Code:
<xsl:for-each-group select="*" 
                group-starting-with="*[not(self::dt|self::dd)]">
  <xsl:choose>
  <xsl:when test="self::li[dl]">
     <li>
       <xsl:copy-of select="*"/>
       <xsl:copy-of select="subsequence(current-group(),2)"/>
     </li>
  </xsl:when>
  <xsl:otherwise>
     <xsl:copy-of select="current-group()"/>
  </xsl:otherwise>
  </xsl:choose>
</xsl:for-each-group>
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
Reply With Quote
  #3 (permalink)  
Old April 10th, 2011, 11:52 AM
Registered User
 
Join Date: Apr 2011
Posts: 8
Thanks: 3
Thanked 0 Times in 0 Posts
Default

Thanks already for this Michael, but unfortunatly I seem to miss something as whatever I try only the 'otherwise' part is called, so I basically get a perfect copy of my original code but without the hoped for changes. Any clue what I'm missing ?

xslt used :

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:for-each-group select="*" group-starting-with="*[not(self::dt|self::dd)]">
<xsl:choose>
<xsl:when test="self::li[dl]">
<li>
<xsl:copy-of select="*"/>
<xsl:copy-of select="subsequence(current-group(),2)"/>
</li>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>


Original xml :

<test>
<li><dl><dt>blabla</dt></dl></li>
<dd>blablabla</dd>
<dt>blablabla</dt>
<li><dl><dt>blabla2</dt></dl></li>
<dd>blablabla2</dd>
<dt>blablabla2</dt>
<!-- no dd for this one -->
<dt>blablabla3</dt>
<dd>blablabla3</dd>
<p>some other element</p>
<!-- rest of the data -->
</test>

Wanted outcome :

<test>
<li>
<dl>
<dt>blabla</dt>
<dd>blablabla</dd>
<dt>blablabla</dt>
</dl>
</li>
<li>
<dl>
<dt>blabla2</dt>
<dd>blablabla2</dd>
<dt>blablabla2</dt>
<!-- no dd for this one -->
<dt>blablabla3</dt>
<dd>blablabla3</dd>
</dl>
</li>
<p>some other element</p>
<!-- rest of the data -->
</test>
Reply With Quote
  #4 (permalink)  
Old April 10th, 2011, 11:57 AM
Friend of Wrox
Points: 6,676, Level: 34
Points: 6,676, Level: 34 Points: 6,676, Level: 34 Points: 6,676, Level: 34
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Nov 2007
Location: Germany
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

Well with template match="/" the for-each-group select="*" selects only the root element "test" so you might want template match="test" instead or you need to change the select to select="test/*"
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
Reply With Quote
  #5 (permalink)  
Old April 10th, 2011, 12:21 PM
Friend of Wrox
Points: 6,676, Level: 34
Points: 6,676, Level: 34 Points: 6,676, Level: 34 Points: 6,676, Level: 34
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Nov 2007
Location: Germany
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

Here is a stylesheet sample with the suggestion I already did (match="test") plus some slight change of Michael's code to ensure the "dl" wraps the grouped elements:
Code:
<xsl:stylesheet 
  version="2.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">
  
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  
  <xsl:template match="test">
    <xsl:copy>
      <xsl:for-each-group select="*" group-starting-with="*[not(self::dt|self::dd)]">
        <xsl:choose>
          <xsl:when test="self::li[dl]">
            <li>
              <dl>
                <xsl:copy-of select="dl/*"/>
                <xsl:copy-of select="subsequence(current-group(),2)"/>
              </dl>
            </li>
          </xsl:when>
          <xsl:otherwise>
            <xsl:copy-of select="current-group()"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>
Saxon 9, when applied to the XML input sample
Code:
<test>
<li><dl><dt>blabla</dt></dl></li>
<dd>blablabla</dd>
<dt>blablabla</dt>
<li><dl><dt>blabla2</dt></dl></li>
<dd>blablabla2</dd>
<dt>blablabla2</dt>
<!-- no dd for this one -->
<dt>blablabla3</dt>
<dd>blablabla3</dd>
<p>some other element</p>
<!-- rest of the data -->
</test>
outputs
Code:
<test>
   <li>
      <dl>
         <dt>blabla</dt>
         <dd>blablabla</dd>
         <dt>blablabla</dt>
      </dl>
   </li>
   <li>
      <dl>
         <dt>blabla2</dt>
         <dd>blablabla2</dd>
         <dt>blablabla2</dt>
         <dt>blablabla3</dt>
         <dd>blablabla3</dd>
      </dl>
   </li>
   <p>some other element</p>
</test>
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
Reply With Quote
  #6 (permalink)  
Old April 10th, 2011, 02:37 PM
Registered User
 
Join Date: Apr 2011
Posts: 8
Thanks: 3
Thanked 0 Times in 0 Posts
Thumbs up

Great, works perfectly now ! Thanks a lot guy's, highly appreciated
Reply With Quote
  #7 (permalink)  
Old May 12th, 2011, 01:08 PM
Registered User
 
Join Date: May 2011
Posts: 3
Thanks: 0
Thanked 0 Times in 0 Posts
Default Is there a way of doing this in XSLT 1.1?

Hello,

Can anyone suggest a means of implementing this in XSLT1.1?

Thanks so much in advance.
Reply With Quote
  #8 (permalink)  
Old May 12th, 2011, 01:11 PM
Friend of Wrox
Points: 6,676, Level: 34
Points: 6,676, Level: 34 Points: 6,676, Level: 34 Points: 6,676, Level: 34
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Nov 2007
Location: Germany
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

XSLT 1.1? There is XSLT 1.0 and 2.0 as specifications but 1.1 never became one.
As for grouping with XSLT 1.0, you could search for examples on "sibling recursion".
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
Reply With Quote
  #9 (permalink)  
Old May 12th, 2011, 01:56 PM
Registered User
 
Join Date: May 2011
Posts: 3
Thanks: 0
Thanked 0 Times in 0 Posts
Default Sibling recursion group by attribute

Hi Martin thanks so much for your reply, yes my mistake I did mean 1.0. (I'm new to XSLT)

The slight variation I have is that the siblings are of the same node type and I want to group by an attribute (in this case NAME). There are two levels to the grouping - elements within OBR and those within OBX.

Go from this:

<?xml version="1.0" encoding="utf-8" ?>
<Message>
<SEGMENT NAME="OBR"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="OBR"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
</Message>

To this:

<?xml version="1.0" encoding="utf-8" ?>
<Message>
<SEGMENT NAME="OBR">
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="OBX">
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
</SEGMENT>
<SEGMENT NAME="OBX">
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
</SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
</SEGMENT>
<SEGMENT NAME="OBR">
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="OBX">
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
</SEGMENT>
<SEGMENT NAME="OBX">
<SEGMENT NAME="NTE"></SEGMENT>
<SEGMENT NAME="NTE"></SEGMENT>
</SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
<SEGMENT NAME="OBX"></SEGMENT>
</SEGMENT>
</Message>

Thanks again.
Reply With Quote
  #10 (permalink)  
Old May 13th, 2011, 06:14 AM
Friend of Wrox
Points: 6,676, Level: 34
Points: 6,676, Level: 34 Points: 6,676, Level: 34 Points: 6,676, Level: 34
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Nov 2007
Location: Germany
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

Here is an attempt to solve that grouping task in XSLT 1.0 using keys:
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  
  <xsl:output indent="yes"/>
  
  <xsl:key name="k1" 
    match="SEGMENT[not(@NAME = 'OBR')]" 
    use="generate-id(preceding-sibling::SEGMENT[@NAME = 'OBR'][1])"/>
    
  <xsl:key name="k2"
    match="SEGMENT[not(@NAME = 'OBR') and not(@NAME = 'OBX')]"
    use="concat(generate-id(preceding-sibling::SEGMENT[@NAME = 'OBR'][1]), '|', 
                generate-id(preceding-sibling::SEGMENT[@NAME = 'OBX'][1]))"/>
  
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="Message">
    <xsl:copy>
      <xsl:apply-templates select="SEGMENT[@NAME = 'OBR']"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="SEGMENT[@NAME = 'OBR']">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates 
      select="key('k1', generate-id())[
                generate-id(following-sibling::SEGMENT[@NAME = 'OBX'][1]) =
                generate-id(current()/following-sibling::SEGMENT[@NAME = 'OBX'][1])]
              | key('k1', generate-id())[@NAME = 'OBX']"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="SEGMENT[@NAME = 'OBX']">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates 
        select="key('k2', concat(generate-id(preceding-sibling::SEGMENT[@NAME = 'OBR'][1]), '|',
                           generate-id()))"/>
    </xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>
When applied to the input document
Code:
<?xml version="1.0" encoding="utf-8" ?>
<Message>
  <SEGMENT NAME="OBR"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="OBX"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="OBX"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="OBX"></SEGMENT>
  <SEGMENT NAME="OBX"></SEGMENT>
  <SEGMENT NAME="OBR"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="OBX"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="OBX"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="NTE"></SEGMENT>
  <SEGMENT NAME="OBX"></SEGMENT>
  <SEGMENT NAME="OBX"></SEGMENT>
</Message>
the output is
Code:
<Message>
   <SEGMENT NAME="OBR">
      <SEGMENT NAME="NTE"/>
      <SEGMENT NAME="OBX">
         <SEGMENT NAME="NTE"/>
         <SEGMENT NAME="NTE"/>
         <SEGMENT NAME="NTE"/>
      </SEGMENT>
      <SEGMENT NAME="OBX">
         <SEGMENT NAME="NTE"/>
         <SEGMENT NAME="NTE"/>
      </SEGMENT>
      <SEGMENT NAME="OBX"/>
      <SEGMENT NAME="OBX"/>
   </SEGMENT>
   <SEGMENT NAME="OBR">
      <SEGMENT NAME="NTE"/>
      <SEGMENT NAME="OBX">
         <SEGMENT NAME="NTE"/>
         <SEGMENT NAME="NTE"/>
         <SEGMENT NAME="NTE"/>
      </SEGMENT>
      <SEGMENT NAME="OBX">
         <SEGMENT NAME="NTE"/>
         <SEGMENT NAME="NTE"/>
      </SEGMENT>
      <SEGMENT NAME="OBX"/>
      <SEGMENT NAME="OBX"/>
   </SEGMENT>
</Message>
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
Reply With Quote
The Following User Says Thank You to Martin Honnen For This Useful Post:
srinathe (November 18th, 2013)
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
Grouping following siblings bonekrusher XSLT 5 November 19th, 2009 02:30 PM
Problem accessing the following siblings. Tre XSLT 6 June 5th, 2007 11:13 AM
Comparing siblings Chamkaur XSLT 1 June 17th, 2006 09:56 PM
Turning Siblings into Children wolfie78uk XSLT 3 December 22nd, 2005 08:33 AM
Accessing Attributes of Siblings AForgue XSLT 2 November 4th, 2003 01:39 PM



All times are GMT -4. The time now is 11:13 PM.


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