Wrox Programmer Forums
|
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 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 December 13th, 2013, 12:38 AM
Registered User
 
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
 
Old December 14th, 2013, 05:04 AM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 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
 
Old December 14th, 2013, 05:17 AM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 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
 
Old December 14th, 2013, 07:08 AM
Registered User
 
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..
 
Old December 14th, 2013, 10:13 PM
Registered User
 
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





Similar Threads
Thread Thread Starter Forum Replies Last Post
for-each-group, group-adjacent confusion CanOfBees XSLT 2 June 28th, 2013 02:50 PM
Xsl: strip-space elements; and 'group-adjacent' ROCXY XSLT 6 July 15th, 2010 08: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





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