Solved

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

Posted on 2004-10-14
5
520 Views
Last Modified: 2006-11-17
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
Comment
Question by:dev-ngps7
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
5 Comments
 
LVL 15

Accepted Solution

by:
dualsoul earned 200 total points
ID: 12308106
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
 

Assisted Solution

by:z38601
z38601 earned 150 total points
ID: 12312815
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
 
LVL 26

Assisted Solution

by:rdcpro
rdcpro earned 150 total points
ID: 12313946
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
 

Author Comment

by:dev-ngps7
ID: 12316807
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
 
LVL 15

Expert Comment

by:dualsoul
ID: 12317422
>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

Enroll in June's Course of the Month

June's Course of the Month is now available! Every 10 seconds, a consumer gets hit with ransomware. Refresh your knowledge of ransomware best practices by enrolling in this month's complimentary course for Premium Members, Team Accounts, and Qualified Experts.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The Client Need Led Us to RSS I recently had an investment company ask me how they might notify their constituents about their newsworthy publications.  Probably you would think "Facebook" or "Twitter" but this is an interesting client.  Their cons…
Introduction In my previous article (http://www.experts-exchange.com/Microsoft/Development/MS-SQL-Server/SSIS/A_9150-Loading-XML-Using-SSIS.html) I showed you how the XML Source component can be used to load XML files into a SQL Server database, us…
In this video we outline the Physical Segments view of NetCrunch network monitor. By following this brief how-to video, you will be able to learn how NetCrunch visualizes your network, how granular is the information collected, as well as where to f…
Do you want to know how to make a graph with Microsoft Access? First, create a query with the data for the chart. Then make a blank form and add a chart control. This video also shows how to change what data is displayed on the graph as well as form…

688 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question