Simplexmlelement and Xpath, Getting Empty Array()

SimpleXmlElement and XPath, getting empty array()

The issue is likely the default namespace. See

  • SimpleXMLElement::registerXPathNamespace
    Creates a prefix/ns context for the next XPath query

Example:

$sxe->registerXPathNamespace('x', 'http://checkout.google.com/schema/2');
$result = $sxe->xpath('//x:notifications');

As an alternative if there is no other namespaces, simply remove the default namespace with

str_replace('xmlns="http://checkout.google.com/schema/2"', '', $raw_xml);

before feeding the XML to your SimpleXmlElement.

Why does SimpleXML XPath return an empty array?

xmlns="http://schema.infor.com/InforOAGIS/2"

This declares a default namespace which you do need to register via registerXPathNamespace() and address the elements using the prefix registered.


Example:

$xml = simplexml_load_file("example.xml");
$xml->registerXPathNamespace('ns', "http://schema.infor.com/InforOAGIS/2");
$displayIds = $xml->xpath('/ns:SyncCustomerPartyMaster/ns:DataArea/ns:CustomerPartyMaster/ns:PartyIDs/ns:DisplayID');

foreach ($displayIds as $displayId) {
echo $displayId;
}

Output:

10100

SimpleXML xpath has empty element

Ok, so I've figured out what was happening, and leaving this here in the hopes that anyone who stumbles across the same issue will find this answer helpful. Thanks to IMSoP for helping me get to the answer.

First, SimpleXML's xpath() method returns an array, not a XML object, which makes life difficult from the outset. Second of all, if you look at the <notifications> tag, you will see it uses the outbound namespace, which in my example I aliased to ob. As such, both Notification and sObject are also considered to be part of the same namespace as they are children of the namespaced element, which means the correct xpath is

//soapenv:Envelope/soapenv:Body/ob:notifications/ob:Notification/ob:sObject

Which, as mentioned above, in SimpleXML will return an array, so grab the first index, and use the provided namespace url for sObject in the children() method call and you will get your data.

So using the above example XML

<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<notifications
xmlns="http://soap.sforce.com/2005/09/outbound">
<OrganizationId>00DN0000000XXXXXXX</OrganizationId>
<ActionId>04kN0000000XXXXXXX</ActionId>
<SessionId xsi:nil="true"/>
<EnterpriseUrl>https://cs6.salesforce.com/services/Soap/c/38.0/00DN0000000XXXXXXX</EnterpriseUrl>
<PartnerUrl>https://cs6.salesforce.com/services/Soap/u/38.0/00DN0000000XXXXXXX</PartnerUrl>
<Notification>
<Id>00DN0000000XXXXXXX</Id>
<sObject xsi:type="sf:Account"
xmlns:sf="urn:sobject.enterprise.soap.sforce.com">
<sf:Id>00DN0000000XXXXXXX</sf:Id>
<sf:FirstName>Bill</sf:FirstName>
<sf:LastName>Jack</sf:LastName>
<sf:PersonEmail>abc@xyz.net</sf:PersonEmail>
<sf:PersonMobilePhone>0400000000</sf:PersonMobilePhone>
</sObject>
</Notification>
</notifications>
</soapenv:Body>
</soapenv:Envelope>

We can get the data by doing the following:

$xml = simplexml_load_string($xmlString);

$xml->registerXPathNamespace('soapenv', 'http://schemas.xmlsoap.org/soap/envelope/');
$xml->registerXPathNamespace('ob', 'http://soap.sforce.com/2005/09/outbound');

$sObject = $xml->xpath('//soapenv:Envelope/soapenv:Body/ob:notifications/ob:Notification/ob:sObject')[0];

$data = $sObject->children('urn:sobject.enterprise.soap.sforce.com');
var_dump($data);

SimpleXML returns empty arrays

To search for a particular lat do this:

<?php
$xml = simplexml_load_file("http://maps.googleapis.com/maps/api/geocode/xml?address=Brussels,Belgium&sensor=false");
$lat= $xml->xpath("/GeocodeResponse/result/geometry/location/lat");
print_r($lat);
?>

For all lat occurrences specify the path of //lat.

PHP xml to array - how to get rid of empty tags?

Found it out my self. Took a while but works perfectly.

/** 
* @param array|\SimpleXMLElement[]|\SimpleXMLElement $data .
*
* @return array
*/
protected function emptyNodesToNull($data)
{
if ($data instanceof \SimpleXMLElement and $data->count() === 0) {
// is empty object like
// SimpleXMLElement::__set_state(array())
// which was f.e. a <foo/> tag
// or
// SimpleXMLElement::__set_state(array(0 => ' ',))
// which was f.e. a <foo> </foo> (with white space only)
return null;
}
$data = (array)$data;
foreach ($data as &$value) {
if (is_array($value) or $value instanceof \SimpleXMLElement) {
$value = $this->emptyNodesToNull($value);
} else {
// $value is the actual value of a node.
// Could do further checks here.
}
}
return $data;
}

My tests did exactly what i expected

and returns imo exactly what you can expect from a xmlToArray method.

I mean we wont be able to handle attributes, but this is not the requirement.

Test:

    $xml
= '<?xml version="1.0"?>
<Envelope>
<a/><!-- expecting null -->
<foo>
<b/><!-- expecting null -->
<bar>
<baz>Hello</baz>

<!-- expecting here an array of 2 x null -->
<c/>
<c/>

</bar>
</foo>
<foo>
<bar>
<baz>Hello Again</baz>
<d> </d><!-- expecting null -->
<item>
<firstname>Foo</firstname>
<email></email><!-- expecting null -->
<telephone/><!-- expecting null -->
<lastname>Bar</lastname>
</item>
<item>
<firstname>Bar</firstname>
<email>0</email><!-- expecting value 0 (zero) -->
<telephone/><!-- expecting null -->
<lastname>Baz</lastname>
</item>

<!-- expecting array of values 1, 2 null, 4 -->
<number>1</number>
<number>2</number>
<number></number>
<number>4</number>
</bar>
</foo>
</Envelope>';

$xml = new \SimpleXMLElement($xml);
$array = $class::emptyNodesToNull($xml);

Returns:

[
'Envelope' => [
'a' => null,
'foo' => [
0 => [
'b' => null,
'bar' => [
'baz' => 'Hello',
'c' => [
0 => null,
1 => null,
],
],
],
1 => [
'bar' => [
'baz' => 'Hello Again',
'd' => null,
'item' => [
0 => [
'firstname' => 'Foo',
'email' => null,
'telephone' => null,
'lastname' => 'Bar',
],
1 => [
'firstname' => 'Bar',
'email' => '0',
'telephone' => null,
'lastname' => 'Baz',
],
],
'number' => [
0 => '1',
1 => '2',
2 => null,
3 => '4',
],
],
],
],
],
];

simplexml_load_file with xPath returns empty array

It might be rather easy, but I guess it is also the most common mistake in the history of XML: You are forgetting namespaces!

A lot of elements in the given XML are changing the default namespace and you have to consider that in your XPath.

You can first register your namespace like so:

$xml->registerXPathNamespace('y', 'http://maps.yandex.ru/ymaps/1.x');
$xml->registerXPathNamespace('a', 'http://maps.yandex.ru/attribution/1.x');

and then you can query your data:

$xml->xpath('//y:ymaps/y:GeoObjectCollection');

simpleXML returns empty element

How can I access all <a:article> in <f:articles> ?

To me simpleXML seems a bit clumsy when working with namespaces (but then again, I'm not a PHP coder). You could use the children method but in my opinion using XPath is simpler.

$feed->registerXPathNamespace('f', 'http://www.bbgo.de/feedxml/feed');
$feed->registerXPathNamespace('a', 'http://www.bbgo.de/feedxml/article');
$article = feed->xpath("/f:feed/f:articles/a:article");

First you need to register the namespaces with some prefixes (prefixes don't need to match the ones used in the XML document but namespace URIs must match) and then use these prefixes in your XPath expression that selects your target. Without the element name prefixes the XPath expression would look for elements that are not in any namespace.

Why $feed array is not built?

It's hard to guess a reason for this. Some things you could test:

  • is the string $xml a well-formed xml document
  • does creating a SimpleXMLElement with explicit string parameter work $feed = new SimpleXMLElement('<root><child/></root>');
  • can you load your document using simplexml_load_file($yourXMLfile); or simplexml_load_string($yourXMLstring);

SimpleXMLElement and xpath

The declaration in the second line of your XML gives two XML namespaces:

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:zapi="http://zotero.org/ns/api">

The first xmlns attribute, with no colon-separated suffix, sets the "default" namespace for the entry element as being http://www.w3.org/2005/Atom, and descendant nodes with no prefix are in that namespace by default. To be able to access these elements with a "default" namespace using XPath, you need to set a namespace using registerXPathNamespace, and then perform your query, prefixing the element you're looking for (link) with the namespace:

$xml->registerXPathNamespace('d', 'http://www.w3.org/2005/Atom');
$php_up = $xml->xpath("//d:link[@rel='up']");
var_dump($php_up);

Output:

array(1) {
[0] =>
class SimpleXMLElement#2 (1) {
public $@attributes =>
array(3) {
'rel' =>
string(2) "up"
'type' =>
string(20) "application/atom+xml"
'href' =>
string(9) "some link"
}
}
}

Using Xpath in php, return an empty array

This line is incorrect:

$data->registerXPathNamespace('c',$html); 

$html is the URL that you are querying, but in this line you need to specify the namespace of the XML you are using. That namespace is:

http://musicbrainz.org/ns/mmd-2.0#

So use this:

$data->registerXPathNamespace('c','http://musicbrainz.org/ns/mmd-2.0#'); 


Related Topics



Leave a reply



Submit