 |
| 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
|
|
|
|

April 9th, 2011, 04:10 PM
|
|
Registered User
|
|
Join Date: Apr 2011
Posts: 8
Thanks: 3
Thanked 0 Times in 0 Posts
|
|
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 !
|
|

April 9th, 2011, 06:48 PM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
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
|
|

April 10th, 2011, 11:52 AM
|
|
Registered User
|
|
Join Date: Apr 2011
Posts: 8
Thanks: 3
Thanked 0 Times in 0 Posts
|
|
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>
|
|

April 10th, 2011, 11:57 AM
|
|
Friend of Wrox
|
|
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
|
|
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
|
|

April 10th, 2011, 12:21 PM
|
|
Friend of Wrox
|
|
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
|
|
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
|
|

April 10th, 2011, 02:37 PM
|
|
Registered User
|
|
Join Date: Apr 2011
Posts: 8
Thanks: 3
Thanked 0 Times in 0 Posts
|
|
Great, works perfectly now ! Thanks a lot guy's, highly appreciated
|
|

May 12th, 2011, 01:08 PM
|
|
Registered User
|
|
Join Date: May 2011
Posts: 3
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
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.
|
|

May 12th, 2011, 01:11 PM
|
|
Friend of Wrox
|
|
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
|
|
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
|
|

May 12th, 2011, 01:56 PM
|
|
Registered User
|
|
Join Date: May 2011
Posts: 3
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
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.
|
|

May 13th, 2011, 06:14 AM
|
|
Friend of Wrox
|
|
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
|
|
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
|
|
The Following User Says Thank You to Martin Honnen For This Useful Post:
|
|
|
 |