Solved

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

Posted on 2004-10-14
5
505 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
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

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

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

Suggested Solutions

Title # Comments Views Activity
c# advanced xml like string split - Help needed 15 73
Excel VBA and XML - traversing the nodes 2 68
RSS Feed Parsing for Images 5 59
json format text only 4 75
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…
Browsing the questions asked to the Experts of this forum, you will be amazed to see how many times people are headaching about monster regular expressions (regex) to select that specific part of some HTML or XML file they want to extract. The examp…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …
Concerto provides fully managed cloud services and the expertise to provide an easy and reliable route to the cloud. Our best-in-class solutions help you address the toughest IT challenges, find new efficiencies and deliver the best application expe…

948 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

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now