I have a structure similar to the following:
<page id='1'>
<title>Page 1</title>
<page id='2'>
<title>Sub Page 1</title>
</page>
<page id='3'>
<title>Sub Page 2</title>
</page>
</page>
<page id='4'>
<title>Page 2</title>
</page>
I need to select a page by Id but if that page has descendant pages I don't want to return those elements, but I do want the other elements of that page. If I select Page 1 I want to return title but not the child pages...
//page[@id=1]
The above gets me page 1, but how do I exclude the sub pages? Also, There could be any arbitrary number of elements in a page.
//page[@id=1]/*[not(self::page)]
I have found that this gets me the data I want. However, that data comes back as an array of objects with one object per element and apparently excludes the element names???. I am using PHP SimpleXML for what it is worth.
In the div component with an id feature of hero //div[@id='hero'] the following items will be chosen by this XPath expression: //div[@id='hero']/* will choose every one of its children components. //div[@id='hero']/img will choose every children in it.
The child axis indicates the children of the context node. If an XPath expression does not specify an axis, the child axis is understood by default. Since only the root node or element nodes have children, any other use will select nothing.
node() matches any node (the least specific node test of them all) text() matches text nodes only. comment() matches comment nodes. * matches any element node.
If you're only interested in the title element, this would work:
//page[@id=1]/title
If however you need other sub elements of page, I'm not sure XPath is the right tool for you. Sounds more like something that an XSLT would be suited for, since what you are really doing is transforming your data.
Use:
//page[@id=$yourId]/node()[not(self::page)]
This selects all nodes that are not page
and that are children of any page
in the document, the string value of whose id
attribute is equal to the string contained in $yourId
(most probably you would substitute $yourId
above with a specific, desired string, such as '1'
).
Here is a simple XSLT-based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pId" select="3"/>
<xsl:template match="/">
<xsl:copy-of select="//page[@id=$pId]/node()[not(self::page)]"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document (wrapped in a single top node to make it well-formed):
<pages>
<page id='1'>
<title>Page 1</title>
<page id='2'>
<title>Sub Page 1</title>
</page>
<page id='3'>
<title>Sub Page 2</title>
</page>
</page>
<page id='4'>
<title>Page 2</title>
</page>
</pages>
the wanted, correct result is produced:
<title>Sub Page 2</title>
Do note: One assumption made is that an id
value uniquely identifies a page
. If this is not so, the proposed XPath expression will select all page
elements whose id
attribute has a string valu of $yourId
.
If this is the case and only one page
element must be selected, the OP must specify which one of the many page
elements with this id
should be selected.
For example, it may be the first:
(//page[@id=$yourId]/node()[not(self::page)])[1]
or the last:
(//page[@id=$yourId]/node()[not(self::page)])[last()]
or ...
If the page always has a title:
//page[@id='1']/*[not(boolean(./title))]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With