?
Solved

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

Posted on 2004-10-14
5
Medium Priority
?
526 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 800 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 600 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 600 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

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

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

The Problem How to write an Xquery that works like a SQL outer join, providing placeholders for absent data on the outer side?  I give a bit more background at the end. The situation expressed as relational data Let’s work through this.  I’ve …
I was working on a PowerPoint add-in the other day and a client asked me "can you implement a feature which processes a chart when it's pasted into a slide from another deck?". It got me wondering how to hook into built-in ribbon events in Office.
Michael from AdRem Software outlines event notifications and Automatic Corrective Actions in network monitoring. Automatic Corrective Actions are scripts, which can automatically run upon discovery of a certain undesirable condition in your network.…
Add bar graphs to Access queries using Unicode block characters. Graphs appear on every record in the color you want. Give life to numbers. Hopes this gives you ideas on visualizing your data in new ways ~ Create a calculated field in a query: …

764 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