I have the following xml:
<user>
<section xmlns="ss">Testing</section>
<department xmlns="da">IT</department>
</user>
Now while iterating, i want the namespace information for the tag(ss for section and da for department).
With SimpleXMLIterator, I am not able to get the namespace info for every tag.
Any help would be appreciated
Use the SimpleXMLElement::getNamespaces() method to access the element's namespace(s).
$xml = '
<user>
<section xmlns="ss">Testing</section>
<department xmlns="da">IT</department>
</user>
';
$iterator = new SimpleXMLIterator($xml);
foreach ($iterator as $element) {
var_dump($element->getNamespaces());
}
Outputs (along with lots of warnings because of your broken XML):
array(1) {
[""]=>
string(6) "ss"
}
array(1) {
[""]=>
string(6) "da"
}
Related
I'm not sure if this is the expected behavior or if I'm doing something wrong:
<?php
$xml = '<?xml version="1.0"?>
<foobar>
<foo>
<nested>
<img src="example1.png"/>
</nested>
</foo>
<foo>
<nested>
<img src="example2.png"/>
</nested>
</foo>
</foobar>';
$dom = new DOMDocument();
$dom->loadXML($xml);
$node = $dom->getElementsByTagName('foo')[0];
$simplexml = simplexml_import_dom($node);
echo $simplexml->asXML() . "\n";
echo " === With // ====\n";
var_dump($simplexml->xpath('//img'));
echo " === With .// ====\n";
var_dump($simplexml->xpath('.//img'));
Even though I only imported a specific DomNode, and asXml() returns only that part, the xpath() still seems to operate on the whole document.
I can prevent that by using .//img, but that seemed rather strange to me.
Result:
<foo>
<nested>
<img src="example1.png"/>
</nested>
</foo>
=== With // ====
array(2) {
[0] =>
class SimpleXMLElement#4 (1) {
public $#attributes =>
array(1) {
'src' =>
string(12) "example1.png"
}
}
[1] =>
class SimpleXMLElement#5 (1) {
public $#attributes =>
array(1) {
'src' =>
string(12) "example2.png"
}
}
}
=== With .// ====
array(1) {
[0] =>
class SimpleXMLElement#5 (1) {
public $#attributes =>
array(1) {
'src' =>
string(12) "example1.png"
}
}
}
It is expected behavior. You're importing an DOM element node into an SimpleXMLElement. This does not modify the XML document in the background - the node keeps its context.
Here are Xpath expressions that go up (parent::, ancestor::) or to siblings (preceding-sibling::, following-sibling::).
Location paths starting with a / are always relative to the document, not the context node. An explicit reference to the current node with the . avoids that trigger. .//img is short for current()/descendant-or-self::img - an alternative would be descendant::img.
However you don't need to convert the DOM node into a SimpleXMLElement to use Xpath.
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
foreach ($xpath->evaluate('//foo[1]') as $foo) {
var_dump(
$xpath->evaluate('string(.//img/#src)', $foo)
);
}
Output:
string(12) "example1.png"
//foo[1] fetches the first foo element node in the document. If here is no matching element in the document it will return an empty list. Using foreach allows to avoid an error in that case. It will be iterated once or never.
string(.//img/#src) fetches the src attribute of descendant img elements and casts the first one into a string. If here is no matching node the return value will be and empty string. The second argument to DOMXpath::evaluate() is the context node.
I read the following XML:
http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml
controller:
$ECB_rates = array();
$currencies = explode(',', 'GBP,USD,RUB,AUD');
foreach($currencies as $currency) {
$ECB_rates[$currency] = $ECB_XML->xpath('//Cube/Cube/Cube[#currency="' . $currency . '"]/#rate');
}
$this->set('ECB_rates', $ECB_rates);
view:
var_dump($ECB_rates);
and I get the following:
array(4) { ["GBP"]=> array(0) { } ["USD"]=> array(0) { } ["RUB"]=> array(0) { } ["AUD"]=> array(0) { } }
I can't figure out why the rates are returned as empty array.
The document has a default namespace, which can commonly confuse XPath expressions if not done correctly. The other part is that xpath() returns an array of SimpleXMLElements which also doesn't help your cause.
The following code first registers the default namespace with the prefix 'def' and then (I've simplified the expression as well) uses this as a prefix to find the <Cube> element with the currency you want. Then it takes the first result (there should only be one anyway) and casts it to a string to make it more useful.
$ECB_XML->registerXPathNamespace("def", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
$ECB_rates = array();
$currencies = explode(',', 'GBP,USD,RUB,AUD');
foreach($currencies as $currency) {
$ECB_rates[$currency] = (string)$ECB_XML->xpath('//def:Cube[#currency="' . $currency . '"]/#rate')[0];
}
var_dump($ECB_rates);
Which gives...
array(4) {
'GBP' =>
string(7) "0.87295"
'USD' =>
string(6) "1.2234"
'RUB' =>
string(7) "70.8270"
'AUD' =>
string(6) "1.5934"
}
Update:
You could (if the XML format is stable) just use SimpleXML element/attribute access. Checking if the currency is in the array of currencies your after...
foreach($ECB_XML->Cube->Cube->Cube as $rate) {
$currency = (string)$rate["currency"];
if ( in_array($currency, $currencies)) {
$ECB_rates[$currency] = (string)$rate["rate"];
}
}
This may give you the items in a different order, but this might not be an issue.
I have XML documents containing information of articles, that have a kind of hierarchy:
<?xml version="1.0" encoding="UTF-8"?>
<page>
<elements>
<element>
<type>article</type>
<id>1</id>
<parentContainerID>page</parentContainerID>
<parentContainerType>page</parentContainerType>
</element>
<element>
<type>article</type>
<id>2</id>
<parentContainerID>1</parentContainerID>
<parentContainerType>article</parentContainerType>
</element>
<element>
<type>photo</type>
<id>3</id>
<parentContainerID>2</parentContainerID>
<parentContainerType>article</parentContainerType>
</element>
<... more elements ..>
</elements>
</page>
The element has the node parentContainerID and the node parentContainerType. If parentContainerType == page, this is the master element. The parentContainerID shows what's the element's master. So it should look like: 1 <- 2 <- 3
Now I need to build a new page (html) of this stuff that looks like this:
content of ID 1, content of ID 2, content of ID 3 (the IDs are not ongoing).
I guess this could be done with a recursive function. But I have no idea how to manage this?
Here is no nesting/recursion in the XML. The <element/> nodes are siblings. To build the parent child relations I would suggest looping over the XML and building two arrays. One for the relations and one referencing the elements.
$xml = file_get_contents('php://stdin');
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$relations = [];
$elements = [];
foreach ($xpath->evaluate('//element') as $element) {
$id = (int)$xpath->evaluate('string(id)', $element);
$parentId = (int)$xpath->evaluate('string(parentContainerID)', $element);
$relations[$parentId][] = $id;
$elements[$id] = $element;
}
var_dump($relations);
Output:
array(3) {
[0]=>
array(1) {
[0]=>
int(1)
}
[1]=>
array(1) {
[0]=>
int(2)
}
[2]=>
array(1) {
[0]=>
int(3)
}
}
The relations array now contains the child ids for any parent, elements without a parent are in index 0. This allows you use a recursive function access the elements as a tree.
function traverse(
int $parentId, callable $callback, array $elements, array $relations, $level = -1
) {
if ($elements[$parentId]) {
$callback($elements[$parentId], $parentId, $level);
}
if (isset($relations[$parentId]) && is_array($relations[$parentId])) {
foreach ($relations[$parentId] as $childId) {
traverse($childId, $callback, $elements, $relations, ++$level);
}
}
}
This executes the callback for each node. The proper implementation for this would be a RecursiveIterator but the function should do for the example.
traverse(
0,
function(DOMNode $element, int $id, int $level) use ($xpath) {
echo str_repeat(' ', $level);
echo $id, ": ", $xpath->evaluate('string(type)', $element), "\n";
},
$elements,
$relations
);
Output:
1: article
2: article
3: photo
Notice that the $xpath object is provided as context to the callback. Because the $elements array contains the original nodes, you can use Xpath expression to fetch detailed data from the DOM related to the current element node.
I want to get the value '23452345235' of the parameter with name="userID" from this xml:
<?xml version="1.0" encoding="UTF-8"?>
<callout>
<parameter name="UserID">
23452345235
</parameter>
<parameter name="AccountID">
57674567567
</parameter>
<parameter name="NewUserID">
54745674566
</parameter>
</callout>
I'm using this code:
$xml = simplexml_load_string($data);
$myDataObject = $xml->xpath('//parameter[#name="UserID"]');
var_dump($myDataObject);
And I'm getting this:
array(1) {
[0] =>
class SimpleXMLElement#174 (1) {
public $#attributes =>
array(1) {
'name' =>
string(6) "UserID"
}
}
}
I actually want to get the value of '23452345235' or receive the parameter in order to get this value.
What I'm doing wrong?
Well you can (optionally) put it under a loop. Like this:
$myDataObject = $xml->xpath('//parameter[#name="UserID"]');
foreach($myDataObject as $element) {
echo $element;
}
Or directly:
echo $myDataObject[0];
Actually is quite straightforward, as seen on your var_dump(), its an array, so access it as such.
SimpleXMLElement::xpath() can only return an array of SimpleXMLElement objects, so it generates an element and attaches the fetched attribute to it.
DOMXpath::evaluate() can return scalar values from Xpath expressions:
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
var_dump($xpath->evaluate('normalize-space(//parameter[#name="UserID"])'));
Output:
string(11) "23452345235"
I'm trying to read the XML using DOM parser. My XML is dynamic so I cannot say always all values / tags will present in the XML. In this case i need to check the tag whether it is exists or not before reading it.
I tried like this as well
if($val->getElementsByTagName("cityName") != null) {
}
if(!empty($val->getElementsByTagName("cityName"))) {
}
getting error : Fatal error: Call to a member function getElementsByTagName() on a non-object in F:\xampp\htdocs\test\static-data.php on line 160
Any solution to find the tag existence. As like has Attribute as we check weather Attribute is there or not.
If you use Xpath to fetch the nodes, you can avoid the validation.
Load some XML and create an DOMXpath instance for it.
$xml = <<<XML
<phoneNumbers>
<phoneNumber type="home">212 555-1234</phoneNumber>
<phoneNumber type="fax">646 555-4567</phoneNumber>
</phoneNumbers>
XML;
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
Get the "home" phone number:
var_dump(
$xpath->evaluate('string(/phoneNumbers/phoneNumber[#type="home"])')
);
Output:
string(12) "212 555-1234"
No "mobile" number exists, so the result is an empty string
var_dump(
$xpath->evaluate('string(/phoneNumbers/phoneNumber[#type="mobile"])')
);
Output:
string(0) ""
You can count the numbers:
var_dump(
[
'all' => $xpath->evaluate('count(/phoneNumbers/phoneNumber)'),
'home' => $xpath->evaluate('count(/phoneNumbers/phoneNumber[#type="home"])'),
'fax' => $xpath->evaluate('count(/phoneNumbers/phoneNumber[#type="fax"])'),
'mobile' => $xpath->evaluate('count(/phoneNumbers/phoneNumber[#type="mobile"])')
]
);
Output:
array(4) {
["all"]=>
float(2)
["home"]=>
float(1)
["fax"]=>
float(1)
["mobile"]=>
float(0)
}
Or iterate the numbers
foreach ($xpath->evaluate('/phoneNumbers/phoneNumber') as $phoneNumber) {
var_dump(
$phoneNumber->getAttribute('type'),
$phoneNumber->nodeValue
);
}
Output:
string(4) "home"
string(12) "212 555-1234"
string(3) "fax"
string(12) "646 555-4567"
Full example: https://eval.in/123212