Jamie Green wrote:
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:output 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