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 30th, 2009, 01:20 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

It's very easy in XSLT 2.0 using the <xsl:for-each-group> instruction.

In XSLT 1.0 it's possible, but convoluted: look up "Muenchian grouping" in your favourite XSLT textbook or on Google.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
 
Old January 30th, 2009, 01:22 PM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

Your description is not quite clear to me. Why does the Group name="SomeTypeA" not have Item element children? And why do the Variable elements in Group name="SomeTypeB" have the contents 'A' in the result you posted?

On the other hand what you seem to want to do is group your elements and that is certainly possible in XSLT. Here is an XSLT 2.0 stylesheet
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">
  
  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="Request">
    <Group name="Request">
      <xsl:for-each-group select="*" group-by="name()">
        <Group name="{current-grouping-key()}">
          <xsl:apply-templates select="current-group()/Variable"/>
        </Group>
      </xsl:for-each-group>
    </Group>
  </xsl:template>
  
  <xsl:template match="Variable">
    <Item>
      <xsl:copy-of select="."/>
    </Item>
  </xsl:template>

</xsl:stylesheet>
that produces the following result:
Code:
<Group name="Request">
   <Group name="SomeTypeA">
      <Item>
         <Variable>A</Variable>
      </Item>
   </Group>
   <Group name="SomeTypeB">
      <Item>
         <Variable>B</Variable>
      </Item>
      <Item>
         <Variable>B</Variable>
      </Item>
   </Group>
</Group>
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
 
Old January 30th, 2009, 01:33 PM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

Quote:
Originally Posted by Martin Honnen View Post
Your description is not quite clear to me. Why does the Group name="SomeTypeA" not have Item element children? And why do the Variable elements in Group name="SomeTypeB" have the contents 'A' in the result you posted?

On the other hand what you seem to want to do is group your elements and that is certainly possible in XSLT. Here is an XSLT 2.0 stylesheet
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">
 
  <xsl:output method="xml" indent="yes"/>
 
  <xsl:template match="Request">
    <Group name="Request">
      <xsl:for-each-group select="*" group-by="name()">
        <Group name="{current-grouping-key()}">
          <xsl:apply-templates select="current-group()/Variable"/>
        </Group>
      </xsl:for-each-group>
    </Group>
  </xsl:template>
 
  <xsl:template match="Variable">
    <Item>
      <xsl:copy-of select="."/>
    </Item>
  </xsl:template>
 
</xsl:stylesheet>
that produces the following result:
Code:
<Group name="Request">
   <Group name="SomeTypeA">
      <Item>
         <Variable>A</Variable>
      </Item>
   </Group>
   <Group name="SomeTypeB">
      <Item>
         <Variable>B</Variable>
      </Item>
      <Item>
         <Variable>B</Variable>
      </Item>
   </Group>
</Group>
Hi Martin,

In the description, <SomeTypeA> does not have <Item> tags because only those nodes with two or more of the same name should be group together and <Item> tags use. Individual nodes under <Request> should not be group together. The actual nodes and their values under <SomeTypeB> don't really matter as long as they are group with all other nodes <SomeTypeB> and their contents separated by <Item> and overall grouped by <group nama = SomeTypeB>.

I hope this is clear. Now your solution output is exactly what I'm looking for but I have to use XSLT 1.0 - does that mean your solution doens't hold or can it still work with 1.0?

Thanks!
 
Old January 31st, 2009, 09:33 AM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

xsl:for-each-group is new in XSLT 2.0, with XSLT 1.0 you therefore can't use that instruction and have to use a technique named Muenchian grouping, see http://www.jenitennison.com/xslt/grouping/muenchian.xml.
Here is an XSLT 1.0 stylesheet that does that for your input XML, also taking into account the clarification that you don't want Item wrapper elements when only a single Variable element exists:
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  
  <xsl:output method="xml" indent="yes"/>
  
  <xsl:key name="by-name" match="Request/*" use="name()"/>
  
  <xsl:template match="Request">
    <Group name="Request">
      <xsl:apply-templates select="*[generate-id() = generate-id(key('by-name', name())[1])]"/>
    </Group>
  </xsl:template>
  
  <xsl:template match="Request/*">
    <Group name="{name()}">
      <xsl:choose>
        <xsl:when test="key('by-name', name())[2]">
          <xsl:apply-templates select="key('by-name', name())/Variable"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:copy-of select="Variable"/>
        </xsl:otherwise>
      </xsl:choose>
    </Group>
  </xsl:template>
  
  <xsl:template match="Variable">
    <Item>
      <xsl:copy-of select="."/>
    </Item>
  </xsl:template>

</xsl:stylesheet>
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
 
Old January 31st, 2009, 10:29 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

Quote:
Originally Posted by Martin Honnen View Post
xsl:for-each-group is new in XSLT 2.0, with XSLT 1.0 you therefore can't use that instruction and have to use a technique named Muenchian grouping, see http://www.jenitennison.com/xslt/grouping/muenchian.xml.
Here is an XSLT 1.0 stylesheet that does that for your input XML, also taking into account the clarification that you don't want Item wrapper elements when only a single Variable element exists:
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
 
  <xsl:output method="xml" indent="yes"/>
 
  <xsl:key name="by-name" match="Request/*" use="name()"/>
 
  <xsl:template match="Request">
    <Group name="Request">
      <xsl:apply-templates select="*[generate-id() = generate-id(key('by-name', name())[1])]"/>
    </Group>
  </xsl:template>
 
  <xsl:template match="Request/*">
    <Group name="{name()}">
      <xsl:choose>
        <xsl:when test="key('by-name', name())[2]">
          <xsl:apply-templates select="key('by-name', name())/Variable"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:copy-of select="Variable"/>
        </xsl:otherwise>
      </xsl:choose>
    </Group>
  </xsl:template>
 
  <xsl:template match="Variable">
    <Item>
      <xsl:copy-of select="."/>
    </Item>
  </xsl:template>
 
</xsl:stylesheet>
Wow this is excellent Martin. I was also using keys trying to get it to work but still couldnt get it perfectly working. I have one more question. I am new to XSLT so this is a lot to take in at the moment. I need to add additional brackets around this code. I.e. this code above will be encapusalate by another group. But if I try putting that group around your code it produces completely wrong results.

For example if I put <Info> </Info> tags around your code to encapsulate the results it causes problems - same with <xsl: element> tags. Is this because your templates must be executed in sequential order? Why don't they have names for example so that they can be called from another file for example. Can I create another XSLT file which does some processing originially with tags and then calls your entire code and execute it? Or can I just add names to your templates and call them one by one?

Thanks again, this is great help!

Jiggs
 
Old January 31st, 2009, 10:48 AM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

Sorry, your description doesn't make it clear to me what you are trying to achieve respectively what kind of changes you want to make. You might want to show the XML output you want to create now.

Nodes are processed by xsl:apply-templates, then the XSLT processor chooses matching templates, that is how XSLT works. If you really want you can name your templates and then call them using xsl:call-template but that is a different approach and I doubt you will get far that way.

If you want to compose your stylesheets from modules you can do that using xsl:import or xsl:include. Naming templates is not necessary to do that.
__________________
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:
jiggs (January 31st, 2009)
 
Old January 31st, 2009, 11:20 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

Quote:
Originally Posted by Martin Honnen View Post
Sorry, your description doesn't make it clear to me what you are trying to achieve respectively what kind of changes you want to make. You might want to show the XML output you want to create now.

Nodes are processed by xsl:apply-templates, then the XSLT processor chooses matching templates, that is how XSLT works. If you really want you can name your templates and then call them using xsl:call-template but that is a different approach and I doubt you will get far that way.

If you want to compose your stylesheets from modules you can do that using xsl:import or xsl:include. Naming templates is not necessary to do that.
Hi Martin,

Thanks for the great help here. You have no idea how much I appreciate it. I've been crash learning XSLT for the first time this week and have inherited some rather ugly code. I undertand most of your code but I still think I dont fully understand it all. Here is what I am exactly trying to accomplish:

XML input:

Code:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
     xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
    <callFlowRequest>
      <Client xmlns="">Company</Client>
      <LocaleID xmlns="">E</LocaleID>
      <User>101</User>
      <request>
        <Level1>
          <Level2>
            <SomeTypeA>
              <Att1>A</Att1>
            </SomeTypeA>
            <SomeTypeB>
              <Att2>B1</Att2>
            </SomeTypeB>
            <SomeTypeB>
              <Att2>B2</Att2>
            </SomeTypeB>
          </Level2>
        </Level1>
      </request>
   </callFlowRequest>
  </soapenv:Body>
</soapenv:Envelope>

The output:

Code:
 
<?xml version="1.0" encoding="UTF-8"?>
<StepData>
  <Flow>
      <ProcessClassID>AGHExtractProc</ProcessClassID>
      <LocaleID>E</LocaleID>
  </Flow>
  <Variables>
  <Group name="Body">
    <Group name="callFlowRequest">
       <Variable name="User">
              <Value>101</Value>
        </Variable>
         <Group name="Request">
             <Group name="SomeTypeA">
                 <Variable name = Att1>
                        <Value>A</Value>
                </Variable>    
             </Group>
             <Group name="SomeTypeB">
                 <Item>
                     <Variable name = Att2>
                        <Value>B1</Value>
                     </Variable>
                 </Item>
                  <Item>
                     <Variable name = Att2>
                        <Value>B2</Value>
                     </Variable>
                 </Item>    
             </Group>
         </Group>   
       </Group>
   </Group>
 </Variables>
</StepData>

So your code handles the touch part of "Level2" and grouping of the same nodes. But there is still some sutff which needs to be processed before we get to this point and a lot more encapusations as you can see from the request that your code builds.

The current code builds all the other stuff correctly <StepData><Variables><Groups> until the grouping so I've been trying to incorporate your code to build the middle part but it's blowing up on me. Most of my current code uses templates with names and then some other parts of the code call it depending on the execution. You code I'm still not sure what this evaluates to:

*[generate-id() = generate-id(key('by-name', name())[1])]

and this

<xsl:when test="key('by-name', name())[2]">

One other thing I've tried to do was in my code to use a key like you but to do For-each node in the key thinking that if my key contained the same nodes I can processes them in a loop and put a <Group> tag around them and <item> tags inside but it never returns any nodes.

Thanks again for any help on this.

Cheers,

J
 
Old January 31st, 2009, 11:28 AM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

Quote:
Originally Posted by Martin Honnen View Post
Sorry, your description doesn't make it clear to me what you are trying to achieve respectively what kind of changes you want to make. You might want to show the XML output you want to create now.

Nodes are processed by xsl:apply-templates, then the XSLT processor chooses matching templates, that is how XSLT works. If you really want you can name your templates and then call them using xsl:call-template but that is a different approach and I doubt you will get far that way.

If you want to compose your stylesheets from modules you can do that using xsl:import or xsl:include. Naming templates is not necessary to do that.
Hi Martin,

I replied with exactly what I'm trying to do but it said the post will need to be reviewed by a moderator before it's visible - not sure why. Can you clarify from your code what do the following evaluate to:

*[generate-id() = generate-id(key('by-name', name())[1])]

key('by-name', name())[2]

Also, why is that when I use the same key like you for example to find specific nodes, when I try to do for-each select = key('name' node()) .. it does find anything. I would expect it to traverse through each node that the key match found?

If I could do that .. then in my code when my current code is traversing all the nodes, once its hits the <request> node then I have a template which gets invoked (in another XSLT) so I was hoping in this template to do my key match and then traverse through the same node with a for-each statement and for all <SomeTypeB> nodes I would easily put a group tag around them and Item tags inside.

Thanks again.

J

 
Old January 31st, 2009, 12:07 PM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

In key('by-name', name())[2] the key function call (as always) returns a node-set, then the predicate [2] (which is equal to [position() = 2]) selects the second node in the node-set. With the xsl:when test="key('by-name', name())[2]" the stylesheet tests whether there are (at least) two elements in the node-set returned by the key function call as in that case you want to wrap each element in an 'Item' element.

The *[generate-id() = generate-id(key('by-name', name())[1])] is the crucial expression for Muenchian grouping to select the first item in each group, please refer to the URL I posted earlier explaining that approach.

As for your attempts with key not giving the result you want, sorry, you need to show exactly what you tried, key('name' node()) is not even syntactically correct. The key defined in my stylesheet has the name 'by-name' and works on the name of elements matching Request/* computed by the name() function so to use it, if the context node is such a child element of the Request element, you can use key('by-name', name()). Or you could do key('by-name', 'SomeTypeB') to get all 'SomeTypeB' elements.
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
 
Old January 31st, 2009, 12:43 PM
Authorized User
 
Join Date: Jan 2009
Posts: 17
Thanks: 2
Thanked 0 Times in 0 Posts
Default

Quote:
Originally Posted by Martin Honnen View Post
In key('by-name', name())[2] the key function call (as always) returns a node-set, then the predicate [2] (which is equal to [position() = 2]) selects the second node in the node-set. With the xsl:when test="key('by-name', name())[2]" the stylesheet tests whether there are (at least) two elements in the node-set returned by the key function call as in that case you want to wrap each element in an 'Item' element.

The *[generate-id() = generate-id(key('by-name', name())[1])] is the crucial expression for Muenchian grouping to select the first item in each group, please refer to the URL I posted earlier explaining that approach.

As for your attempts with key not giving the result you want, sorry, you need to show exactly what you tried, key('name' node()) is not even syntactically correct. The key defined in my stylesheet has the name 'by-name' and works on the name of elements matching Request/* computed by the name() function so to use it, if the context node is such a child element of the Request element, you can use key('by-name', name()). Or you could do key('by-name', 'SomeTypeB') to get all 'SomeTypeB' elements.
In my current code, the XSLT processes the entire input XML node by node. When it hits the <Request> node theres an if statment which calls the following template to process this node:

Code:
<xsl:template name="GroupTable">
<xsl:key name="by-name" match="Request/*" use="SomeTypeB"/>
<xsl:element name="Group">
<xsl:attribute name="name">
<xsl:value-of select="local-name()"/>
</xsl:attribute>
<xsl:copy-of select="@*"/>
<xsl:for-each select="key('by-name', SomeTypeB)">
<xsl:element name = local-name()> <Item>
<xsl:for-each select="node()">
<xsl:call-template name="Variable"/>
</xsl:for-each>
</Item> </xsl:Element>
</xsl:for-each>
</xsl:element>
</xsl:template>
I know the above won't necessarily handle my grouping correctly for non SomeTypeB nodes but I would think that it would at least process all the same nodes. I'm not sure if the second loop is even necessary but Its not even getting into the first loop. I've even tried the select statement as <xsl:for-each select="key('by-name', node())">

I dont understand why it won't find anything. I thought the match should always give you all the nodes that are matched?





Similar Threads
Thread Thread Starter Forum Replies Last Post
dynamic xslt -> xslt creation namespace problem jkmyoung XSLT 2 July 15th, 2006 12:42 AM
Help - I'm Completely Stuck [email protected] ASP.NET 1.x and 2.0 Application Design 1 May 16th, 2006 10:12 AM
Help!! I am completely stuck... andrewba Classic ASP Components 5 May 12th, 2005 04:50 AM
Data Shaping Problem .. im stuck! jeuriks SQL Server ASP 2 March 19th, 2004 11:09 AM
I'm stuck! budman Classic ASP Databases 2 November 3rd, 2003 12:52 AM





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