Finding a set of Relative Nodes

J

Jamie Green

Using MSXML3.0, with the Dom SelectionLanguage set to Xpath I am
trying to query the following document

<Root>
<Child>Name</Child>
<Child>John</Child>
<Child>Smith</Child>
<Child>23</Child>
<Child>Name</Child>
<Child>Peter</Child>
<Child>Jones</Child>
<Child>Name</Child>
<Child>David</Child>
<Child>Brown</Child>
</Root>

I can retrieve the nodeset which contains nodes with the text Name
using the following query:
theRootNode.selectNodes("*[contains(text(),'Name')]")
How can I find all nodes that come one (or two) after a node that
contains the name text? i.e. I want all the first names (nodeset -
John, Peter, David) or all the surnames.
Note positional information will not work - note John Smith has an age
node and the others do not.
Any answers? Is this actually possible using Xpath?
 
E

Ed Beroset

Jamie said:
Using MSXML3.0, with the Dom SelectionLanguage set to Xpath I am
trying to query the following document

<Root>
<Child>Name</Child>
<Child>John</Child>
<Child>Smith</Child>
<Child>23</Child>
<Child>Name</Child>
<Child>Peter</Child>
<Child>Jones</Child>
<Child>Name</Child>
<Child>David</Child>
<Child>Brown</Child>
</Root>

I can retrieve the nodeset which contains nodes with the text Name
using the following query:
theRootNode.selectNodes("*[contains(text(),'Name')]")
How can I find all nodes that come one (or two) after a node that
contains the name text? i.e. I want all the first names (nodeset -
John, Peter, David) or all the surnames.
Note positional information will not work - note John Smith has an age
node and the others do not.
Any answers? Is this actually possible using Xpath?

It's non-trivial, but here's a way to pick out just the age:

Child[not(contains(text(),'Name')) and
not(contains((preceding-sibling::*)[last()],'Name')) and
not(contains((preceding-sibling::*)[last()-1],'Name')) and
contains((preceding-sibling::*)[last()-2],'Name')
]

Hideous, isn't it? :)

Also if it helps, here's how to transform it using XSLT. There are
probably more elegant ways to do this, but I think that if you're able
to run this transform first, subsequent processing would be MUCH easier.

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"<xsl:eek:utput method="xml"
indent="yes" />

<xsl:template match="Root">
<people>
<xsl:apply-templates select="Child" />
</people>
</xsl:template>

<xsl:template match="Child">
<xsl:variable name="namepos">
<xsl:value-of select="position()" />
</xsl:variable>
<xsl:if test="contains(text(),'Name')">
<xsl:element name="person">
<xsl:apply-templates select="//Child[number($namepos+1)]"
mode="firstname" />
<xsl:apply-templates select="//Child[number($namepos+2)]"
mode="lastname" />
<xsl:apply-templates select="//Child[number($namepos+3)]" mode="age" />
</xsl:element>
</xsl:if>
</xsl:template>

<xsl:template match="Child" mode="firstname">
<xsl:if test="not(contains(text(),'Name'))">
<xsl:element name="firstname">
<xsl:value-of select="." />
</xsl:element>
</xsl:if>
</xsl:template>

<xsl:template match="Child" mode="lastname">
<xsl:if test="not(contains(text(),'Name'))">
<xsl:element name="lastname">
<xsl:value-of select="." />
</xsl:element>
</xsl:if>
</xsl:template>

<xsl:template match="Child" mode="age">
<xsl:if test="not(contains(text(),'Name'))">
<xsl:element name="age">
<xsl:value-of select="." />
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Here is the output of the transform when performed on your sample data
above:

<people>
<person>
<firstname>John</firstname>
<lastname>Smith</lastname>
<age>23</age>
</person>
<person>
<firstname>Peter</firstname>
<lastname>Jones</lastname>
</person>
<person>
<firstname>David</firstname>
<lastname>Brown</lastname>
</person>
</people>

Ed
 
D

Dimitre Novatchev [MVP XML]

Jamie Green said:
Using MSXML3.0, with the Dom SelectionLanguage set to Xpath I am
trying to query the following document

<Root>
<Child>Name</Child>
<Child>John</Child>
<Child>Smith</Child>
<Child>23</Child>
<Child>Name</Child>
<Child>Peter</Child>
<Child>Jones</Child>
<Child>Name</Child>
<Child>David</Child>
<Child>Brown</Child>
</Root>

I can retrieve the nodeset which contains nodes with the text Name
using the following query:
theRootNode.selectNodes("*[contains(text(),'Name')]")
How can I find all nodes that come one (or two) after a node that
contains the name text?

Use:

/*/*[. = 'Name']/following-sibling::*[position() &lt; 3]

This will select (when evaluated against your source.xml) the following
nodes:

<Child>John</Child>
<Child>Smith</Child>
<Child>Peter</Child>
<Child>Jones</Child>
<Child>David</Child>
<Child>Brown</Child>


Cheers,

Dimitre Novatchev [XML MVP],
FXSL developer, XML Insider,

http://fxsl.sourceforge.net/ -- the home of FXSL
Resume: http://fxsl.sf.net/DNovatchev/Resume/Res.html
 
J

Jamie Green

Thanks guys - these both make sense.

I have actually changed my approach from the one I originally
requested an answer for (due to knock on effect of handling the
resulting nodesets which would not take account of null queries). A
new problem has arisen however (don't they always).

I am now selecting a set of "Key" nodes (eg. selectnodes("*[. =
'Name']"), and then scanning from each node using the
preceding-sibling and following-sibling methods, matching on position
or relative to a node with some text value.

As part of this, I want to compare the text value of a
following-sibling node with that of the current 'key' node. So, if I
was searching from a node with the value 'John', I want to find the
next one that is called 'John'.

If I use selectnodes("following::sibling*[self::./text() = text()]") I
get all following siblings; I relaise this is due to the 'self'
reference referring to the following-sibling element. How can I refer
back to the original 'Key' node and make this comparison dynamically?

Rgds

Jamie
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,054
Latest member
LucyCarper

Latest Threads

Top