Wrox Programmer Forums

Need to download code?

View our list of code downloads.

Go Back   Wrox Programmer Forums > XML > BOOK: XSLT Programmer's Reference, 2nd Edition
Password Reminder
Register
Register | FAQ | Members List | Calendar | Search | Today's Posts | Mark Forums Read
BOOK: XSLT Programmer's Reference, 2nd Edition
This is the forum to discuss the Wrox book XSLT: Programmer's Reference, 2nd Edition by Michael Kay; ISBN: 9780764543814
Welcome to the p2p.wrox.com Forums.

You are currently viewing the BOOK: XSLT Programmer's Reference, 2nd Edition 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
 
 
Thread Tools Display Modes
  #1 (permalink)  
Old December 13th, 2013, 12:38 AM
Registered User
Points: 20, Level: 1
Points: 20, Level: 1 Points: 20, Level: 1 Points: 20, Level: 1
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Dec 2013
Posts: 5
Thanks: 1
Thanked 0 Times in 0 Posts
Default Group adjacent problem

I've been studying grouping in XSLT 2, and apologise for asking what must seem a simple question, but I havent been able to work it out.

If I wanted to copy all elements in a document (similar to an identity transform) BUT join adjacent 'inline' elements that share an attribute of the same value.

For example: in the sample xml below, the two inline[@style='b'] should be combined into a single inline element, but the first inline should just be copied as should any other elements


Code:
<section>
<p >Some previous paragraph text</p>
<p>
    <inline style="" >(1)</inline>
    <inline style="b">Combine this element</inline>
    <inline style="b">with this element</inline>
</p>
</section>

Any help much appreciated
  #2 (permalink)  
Old December 14th, 2013, 05:04 AM
mhkay's Avatar
Wrox Author
Points: 18,277, Level: 58
Points: 18,277, Level: 58 Points: 18,277, Level: 58 Points: 18,277, Level: 58
Activity: 100%
Activity: 100% Activity: 100% Activity: 100%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,933
Thanks: 0
Thanked 282 Times in 277 Posts
Default

As a general coding pattern for this class of problem:

(a) write a function that matches the nodes you want merged

Code:
<xsl:function name="f:isMerged" as="xs:boolean">
  <xsl:param name="e" as="node()"/>
  <xsl:sequence select="self::inline[@style='b']"/>
</xsl:function>
(b) write a function that does the merging

Code:
<xsl:function name="f:merge" as="node()*">
  <xsl:param name="e" as="node()*"/>
  <inline style="b"><xsl:value-of select="e/string()" separator=""/></inline>
</xsl:function>
(b) use group-adjacent, thus:

Code:
<xsl:for-each-group select="*" group-adjacent="*[f:isMerged(.)]">
  <xsl:sequence select="if (f:isMerged(.)) then f:merge(current-group()) else current-group()"/>
</xsl:for-each>
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
  #3 (permalink)  
Old December 14th, 2013, 05:17 AM
mhkay's Avatar
Wrox Author
Points: 18,277, Level: 58
Points: 18,277, Level: 58 Points: 18,277, Level: 58 Points: 18,277, Level: 58
Activity: 100%
Activity: 100% Activity: 100% Activity: 100%
 
Join Date: Apr 2004
Location: Reading, Berks, United Kingdom.
Posts: 4,933
Thanks: 0
Thanked 282 Times in 277 Posts
Default

As a general coding pattern for this class of problem:

(a) write a function that matches the nodes you want merged

Code:
<xsl:function name="f:isMerged" as="xs:boolean">
  <xsl:param name="e" as="node()"/>
  <xsl:sequence select="self::inline[@style='b']"/>
</xsl:function>
(b) write a function that does the merging

Code:
<xsl:function name="f:merge" as="node()*">
  <xsl:param name="e" as="node()*"/>
  <inline style="b"><xsl:value-of select="e/string()" separator=""/></inline>
</xsl:function>
(b) use group-adjacent, thus:

Code:
<xsl:for-each-group select="*" group-adjacent="*[f:isMerged(.)]">
  <xsl:sequence select="if (f:isMerged(.)) then f:merge(current-group()) else current-group()"/>
</xsl:for-each>
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
  #4 (permalink)  
Old December 14th, 2013, 07:08 AM
Registered User
Points: 20, Level: 1
Points: 20, Level: 1 Points: 20, Level: 1 Points: 20, Level: 1
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Dec 2013
Posts: 5
Thanks: 1
Thanked 0 Times in 0 Posts
Default

HI Mike,
Thank you sincerely for the answer. If I could risk a follow up, working that code into a template and adding a xsl:choose for debugging, in my case I think I have to apply-templates to non-matching groups rather than copy the current-group() (to traverse the xml).

There are 2 problems with the output

a) the xml output - I expected that my comparison function (which has been made a little more complex) would only return true for i elements, but seems to return true for bird and cow, because they are all merged into one in the result xml



b) the comparison function is trying to manage the case of a more dynamic attribute comparison value (@s is not always 'b' but could be 'b i' etc), therefore it is looking at its neighbours before returning a boolean, but why isnt it simply a matter of stating group-adjacent=self::i/@s (ie group the i elements that have the same value in their @s), which would be the behaviour of 'group-by' I think? I get an error if I try that approach.

If you have time for an answer, its appreciated, in any event thanks for the pointers thus far.

XML/XSLT pasted below.


XSLT

Code:
 
   <xsl:stylesheet 
    xmlns:f="http://whetever"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <!-- return true if previous or next node is an 'i' and has the same value for @s-->
    <xsl:function name="f:isMerged" as="xs:boolean">
        <xsl:param name="e" as="node()"/>
        <xsl:sequence select="$e/self::i[@s = preceding-sibling::*[1][self::i]/@s] or $e/self::i[@s = following-sibling::*[1][self::i]/@s]"/>
    </xsl:function>
    
    <xsl:function name="f:merge" as="node()*">
        <xsl:param name="e" as="node()*"/>
        <inline style="b"><xsl:value-of select="$e/string()" separator=""/></inline>
    </xsl:function>
    
    <xsl:template match="*">
        <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:copy-of select="text()" />
            <xsl:for-each-group select="*" group-adjacent="boolean(*[f:isMerged(.)])">
                <!--<xsl:sequence select="if (boolean(f:isMerged(.))) then f:merge(current-group()) else current-group()"/>-->
                <xsl:choose>
                    <!-- current-grouping-key() should be true only for i elements, but bird and cow are in this group -->
                    <xsl:when test="current-grouping-key()">
                        <xsl:sequence select="f:merge(current-group())" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="current-group()"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

    
    
</xsl:stylesheet>
Sample XML

Code:
<code>
    <section>
        <p>text</p>
        <section>
            <i s='b'>join me</i>
            <i s='b'>to me</i>
            <bird>bird</bird>
            <cow>cow</cow>
        </section>
        <para>
            <dog/>
            <cat/>
            <bird/>
            <sub-para>
                <cat>cat</cat>
                <bird/>
            </sub-para>
        </para>
    </section>
</code>

Last edited by indigoXML; December 14th, 2013 at 10:07 PM.
  #5 (permalink)  
Old December 14th, 2013, 10:13 PM
Registered User
Points: 20, Level: 1
Points: 20, Level: 1 Points: 20, Level: 1 Points: 20, Level: 1
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Dec 2013
Posts: 5
Thanks: 1
Thanked 0 Times in 0 Posts
Default

Thanks Michael
The problem I'm having appears to be: attempting to group elements that occur in mixed content such as:

<p><inline css:font-size="11" css:fullstyle="b">Table </inline><inline css:font-size="11" css:fullstyle="b">A:</inline> For Example</p>

ie Combining the two matching 'inline' elements while retaining text content in order
 


Thread Tools
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
for-each-group, group-adjacent confusion CanOfBees XSLT 2 June 28th, 2013 03:50 PM
Xsl: strip-space elements; and 'group-adjacent' ROCXY XSLT 6 July 15th, 2010 09:59 AM
Using - "group-adjacent" ROCXY XSLT 4 January 4th, 2006 11:09 AM
Using Two "group-adjacent" ROCXY XSLT 0 January 4th, 2006 08:29 AM
Combining adjacent elements with the same name susanne_broeenhorst XSLT 2 March 10th, 2005 03:49 AM



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


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