To get the nearest preceding sibling, use this XPath query:
//type[contains(text(), "BBB")]/parent::item/preceding-sibling::item[1]
You need to set the predicate to 1 so as to pick the nearest of the siblings. Otherwise you'd always get the first sibling (for example, if you remove the [1]
predicate, you'll get the AAA
element for both BBB
and CCC
)
Note that the wildcards are not necessary since you presumably already know what the tags are.
$xml = "<root>
<itemList>
<item>
<name>A</name>
<type>AAA</type>
</item>
<item>
<name>B</name>
<type>BBB</type>
</item>
<item>
<name>C</name>
<type>CCC</type>
</item>
</itemList>
</root>";
$xml = new SimpleXMLElement($xml);
$res = $xml->xpath('//type[contains(text(), "BBB")]/parent::item/preceding-sibling::item[1]');
echo "{$res[0]->name} ({$res[0]->type})".PHP_EOL;
$res = $xml->xpath('//type[contains(text(), "CCC")]/parent::item/preceding-sibling::item[1]');
echo "{$res[0]->name} ({$res[0]->type})";
Demo
Result
A (AAA)
B (BBB)
To further illustrate the need to use the predicate, take a look at this:
$xml = "<root>
<itemList>
<item>
<name>A</name>
<type>AAA</type>
</item>
<item>
<name>B</name>
<type>BBB</type>
</item>
<item>
<name>C</name>
<type>CCC</type>
</item>
<item>
<name>C</name>
<type>DDD</type>
</item>
</itemList>
</root>";
$xml = new SimpleXMLElement($xml);
$res = $xml->xpath('//type[contains(text(), "DDD")]/parent::item/preceding-sibling::item');
var_dump($res);
Result
array (size=3)
0 =>
object(SimpleXMLElement)[2]
public 'name' => string 'A' (length=1)
public 'type' => string 'AAA' (length=3)
1 =>
object(SimpleXMLElement)[3]
public 'name' => string 'B' (length=1)
public 'type' => string 'BBB' (length=3)
2 =>
object(SimpleXMLElement)[4]
public 'name' => string 'C' (length=1)
public 'type' => string 'CCC' (length=3)
See how, no matter which element you select with the query, the farthest sibling is always first in the list (and the closest the last)? So, to simulate using the predicate, you could also get the closest sibling simply picking the last element in the array (notice there's no [1]
predicate):
$res = $xml->xpath('//type[contains(text(), "DDD")]/parent::item/preceding-sibling::item');
$total = count($res);
echo "{$res[$total - 1]->name} ({$res[$total - 1]->type})".PHP_EOL;
Demo