Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 538
  • Last Modified:

XSL: Selecting a specific nodeset in a flat structure using XPath

I have the following XML structure:

<root>
  <a>MainText</a>
  <b>SubText</b>
  <c>SubText</c>
  <b>SubText</b>
  <a>MainText</a>
  <b>SubText</b>
  <b>SubText</b>
  <d>SubText</d>
  <a>MainText</a>
  <b>SubText</b>
  <c>SubText</c>
  <b>SubText</b>
</root>

The <a> nodes contain the main content, and all of the subsequent nodes until the next <a> node can be considered to "belong" to the previous <a> node. (I realise that this is not the way to structure an XML document;
"belonging" nodes should be set as children, however, this is what I have to work with.)

I want to form a nodeset of the <a> node and all of the following nodes, up to but not including the next <a> node. In this example I would have 3 separate nodesets.

Nodeset 1:  
  <a>MainText</a>
  <b>SubText</b>
  <c>SubText</c>
  <b>SubText</b>

Nodeset 2:
  <a>MainText</a>
  <b>SubText</b>
  <b>SubText</b>
  <d>SubText</d>

Nodeset 3:
  <a>MainText</a>
  <b>SubText</b>
  <c>SubText</c>
  <b>SubText</b>

I understand that "following-sibling::*" from the first <a> node returns all of the siblings.
Also "following-sibling::a[1]" returns the next <a> node (single node in a nodeset).
What I want is all of the "siblings" in between these two nodes and in this example, I would carry out the process 3 times.

I originally tried using the following:

...
<xsl:template match="a">
     <xsl:for-each select="following-sibling::*">      
            <xsl:if test="not(a)">
                  <xsl:choose>
                        <xsl:when test=" *** name of element ****">
                              <xsl:call-template name="**** do something ****"></xsl:call-template>
                        </xsl:when>            
                  </xsl:choose>
            </xsl:if>
      </xsl:for-each>
</xsl:template>
...


This outputted all of the following siblings(first time through 9 nodes, second time 6 nodes etc.) except the <a>'s. Not what was desired.  I had originally wanted to break out of the "for-each" loop when I encountered an <a> element but realised that this is not possible in XSL. Just to complicate matters a little more, there are variable numbers of <b>, <c> and <d> elements between each <a> element, and the last group is not terminated by an <a> element.

What I think I need is something like:
         <xsl:for-each select="following-sibling::*[***all siblings [up to next 'a' or no more siblings]*** ]">      

However the XPath escapes me!

Any suggestions for the XPath statement or any alternative approaches would be greatly appreciated.

The background to this problem is that each <a> based group will be output to a separate document using <redirect> and Xalan.

Thanks

Mark

0
dev-ngps7
Asked:
dev-ngps7
3 Solutions
 
dualsoulCommented:
hm...i'm not sure about XPath expression to do that, but your task can be handled fairly nice, using recursive template, see:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <xsl:call-template name="processA">
            <xsl:with-param name="node" select="/root/a[1]"/>
        </xsl:call-template>
    </xsl:template>
   
    <xsl:template name="processA">
        <xsl:param name="node"/>
        <xsl:if test="name($node)='a'">
                    Group:<br/>
        </xsl:if>
        <xsl:value-of select="name($node)"/>
        <br/>
        <xsl:if test="$node/following-sibling::*">
            <xsl:call-template name="processA">
                <xsl:with-param name="node" select="$node/following-sibling::*[1]"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>
0
 
z38601Commented:
This should work for you also:

<xsl:for-each select="a">
      <!-- DO SOMETHING, IF NEEDED, WITH THE 'A' NODE -->
      <xsl:for-each select="following-sibling::* and count(preceding-sibling::a[1] | current()) = 1]">
            <!-- DO SOMETHING WITH EACH CHILD NODE -->
      </xsl:for-each>
</xsl:for-each>
0
 
rdcproCommented:
Close...actually you probably meant:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
      <xsl:template match="root">
            <xsl:apply-templates select="a"/>
      </xsl:template>
      <xsl:template match="a">
            <b><xsl:value-of select="local-name()"/>: <xsl:value-of select="."/> </b><br/>
                  <xsl:for-each select="following-sibling::*[count(preceding-sibling::a[1] | current()) = 1][not(local-name() = 'a')]">
                        <em><xsl:value-of select="."/></em><br/>
                  </xsl:for-each>
      </xsl:template>
</xsl:stylesheet>


Good one, z38601!  I was pulling my hair out trying to figure an XPath for this.

Regards,
Mike Sharp
0
 
dev-ngps7Author Commented:
Thanks for the solutions. I have tested them with my example file and all are working exactly as I hoped.

I am going to split the points 3 ways. (Hope that's ok for you all).

The recursive method is the solution that I implemented (because it came in first and I had already made the changes to my stylesheet before seeing the XPath solution. My real application does not consist of <a>'s and <b>'s, and is significantly messier/complicated so adapting it for the recursive method took me a while yesterday.)

In terms of performance, I'm assuming the XPath method would be more efficient, but is this true?

I'm also glad it wasn't just me that was pulling  his hair out over the XPath statement!
Any good tips for advanced XPath tutorials?

Mark
0
 
dualsoulCommented:
>In terms of performance, I'm assuming the XPath method would be more efficient, but is this true

hm...there are count() function is called inside XPath...it's hard to say will it be faster then recursino or not, profiling can say
0

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now