2

I'm expecting an array of Amount, but for some reason it refused to be found. I tried many combinations of setting the name space, but it gives the error SimpleXMLElement::xpath(): Undefined namespace prefix.

Code
      // $xml->registerXPathNamespace("amazon", "http://webservices.amazon.com/AWSECommerceService/2011-08-01");
      // $item->registerXPathNamespace("amazon", "http://webservices.amazon.com/AWSECommerceService/2011-08-01");
      // $possiblePrices = $item->OfferSummary->xpath('//amazon:Amount');
      $possiblePrices = $item->OfferSummary->xpath('//Amount');
      Yii::error(print_r($item->OfferSummary->asXML(), true));
      Yii::error(print_r($possiblePrices, true));
Log
2015-04-15 21:46:06 [::1][-][-][error][application] <OfferSummary><LowestNewPrice><Amount>1</Amount><CurrencyCode>USD</CurrencyCode><FormattedPrice>$0.01</FormattedPrice></LowestNewPrice><TotalNew>15</TotalNew><TotalUsed>0</TotalUsed><TotalCollectible>0</TotalCollectible><TotalRefurbished>0</TotalRefurbished></OfferSummary>
    in /cygdrive/c/Users/Chloe/workspace/xxx/controllers/ShoppingController.php:208
    in /cygdrive/c/Users/Chloe/workspace/xxx/controllers/ShoppingController.php:106
2015-04-15 21:46:06 [::1][-][-][error][application] Array
(
)
Top of XML
<?xml version="1.0" ?>
<ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2011-08-01">
Community
  • 1
  • 1
Chloe
  • 25,162
  • 40
  • 190
  • 357

1 Answers1

3

The Amount element is in the namespace <http://webservices.amazon.com/AWSECommerceService/2011-08-01>. But in your xpath query you put it in no namespace at all. So the example will return 0 elements:

$xml = simplexml_load_string($buffer);

$result = $xml->xpath('//Amount');
printf("Result (%d):\n", count($result));
foreach ($result as $node) {
    echo $node->asXML(), "\n";
}

The output is rather short:

Result (0):

Instead you need to tell that you want the Amount element in that specific namespace. You do that by registering a namespace-prefix and use it in your xpath query:

$xml->registerXPathNamespace("amazon", "http://webservices.amazon.com/AWSECommerceService/2011-08-01");
$result = $xml->xpath('//amazon:Amount');
printf("Result (%d):\n", count($result));
foreach ($result as $node) {
    echo $node->asXML(), "\n";
}

Now the output is with all those elements:

Result (24):
<Amount>217</Amount>
<Amount>1</Amount>
<Amount>800</Amount>
<Amount>1</Amount>
<Amount>498</Amount>
...

Take care that if you've got more than one element-name to check for in your xpath query, that you need to tell the correct namespace for each of them. Example:

$result = $xml->xpath('/*/amazon:Items/amazon:Item//amazon:Amount');

You can find the example code also online here: https://eval.in/314347 (backup)- otherwise the examples from my answer minus the XML at a glance:

$xml = simplexml_load_string($buffer);

$result = $xml->xpath('//Amount');
printf("Result (%d):\n", count($result));
foreach ($result as $node) {
    echo $node->asXML(), "\n";
}

$xml->registerXPathNamespace("amazon", "http://webservices.amazon.com/AWSECommerceService/2011-08-01");
$result = $xml->xpath('//amazon:Amount');
printf("Result (%d):\n", count($result));
foreach ($result as $node) {
    echo $node->asXML(), "\n";
}

$result = $xml->xpath('/*/amazon:Items/amazon:Item//amazon:Amount');
printf("Result (%d):\n", count($result));
foreach ($result as $node) {
    echo $node->asXML(), "\n";
}

Further Readings


If you don't want to register the namespace prefixes over and over again, you can extend from SimpleXMLElement and manage that registration list your own:

/**
 * Class MyXml
 *
 * Example on how to register xpath namespace prefixes within the document
 */
class MyXml extends SimpleXMLElement
{
    public function registerXPathNamespace($prefix, $ns)
    {
        $doc        = dom_import_simplexml($this)->ownerDocument;
        $doc->__sr  = $doc;
        $doc->__d[] = [$prefix, $ns];
    }

    public function xpath($path)
    {
        $doc = dom_import_simplexml($this)->ownerDocument;
        if (isset($doc->__d)) {
            foreach ($doc->__d as $v) {
                parent::registerXPathNamespace($v[0], $v[1]);
            }
        }

        return parent::xpath($path);
    }
}

With this MyXml class you can load the document and then you would only need to register the namespace once:

$namespace = "http://webservices.amazon.com/AWSECommerceService/2011-08-01";

$xml = simplexml_load_file('so-29664051.xml', 'MyXMl');
$xml->registerXPathNamespace("amazon", $namespace);

$item = $xml->Items->Item[0];

$result = $item->xpath('.//amazon:Amount');
printf("Result (%d):\n", count($result));
foreach ($result as $node) {
    echo $node->asXML(), "\n";
}
Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836
  • That wasn't really the problem. I tried using namespaces. See line 1,2,3 of the code. The problem was pointed out by @har07. I thought setting the namespace on an element would propagate/inherit to the children, but it doesn't, so I have to set the namespace on the exact child object that I use `xpath()` with. – Chloe Apr 16 '15 at 18:14
  • 1
    Ah! Yes you have to or you let SimpleXML do that for you by extending from it. I'll add an example on how you can to that. – hakre Apr 16 '15 at 21:18