Lilis Skyremix
Lilis Skyremix

Reputation: 104

PHP/XPATH - Find PRECEDING-SIBLING of PARENT and get its' CHILD

I've been trying to figure this our for days, but I simply cannot seem to get it to work.

Let's say I have an XML file named test.xml like this:

<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>

From PHP, I use SimpleXMLElement to find the node with text BBB.

<?php 
$xmlStr = file_get_contents('test.xml');
$xml = new SimpleXMLElement($xmlStr);
$res = $xml->xpath('//type[contains(text(), "BBB")]/parent::*');

echo "{$res[0]->name} ({$res[0]->type})";
// Result: B (BBB)

Now, I'd like to find the preceding-sibling node of the parent, and get the child nodes' values like A (AAA), but I simply can't figure out how to do so.

Any help would be great.

Thanks.

Upvotes: 2

Views: 3232

Answers (2)

ishegg
ishegg

Reputation: 9937

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

Upvotes: 3

Song
Song

Reputation: 301

See demo https://implode.io/q9OOSC

$xmlStr = <<<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;

$xml = new SimpleXMLElement($xmlStr);

$res = $xml->xpath('//type[contains(text(), "BBB")]/parent::*/preceding-sibling::*');

echo "{$res[0]->name} ({$res[0]->type})";

Upvotes: 1

Related Questions