Using variables as object reference for XML - php

Let's say I have a XML file like this one:
<?xml version="1.0" encoding="utf-8"?>
<test>
<foo>
<bar>Hello, World!</bar>
</foo>
</test>
So, if I use something like this I can echo Hello, World!:
<?php
$xml = simplexml_load_file("myxml.xml");
echo $xml->foo->bar;
?>
But, what if I want to refer to bar with a variable?
<?php
$xml = simplexml_load_file("myxml.xml");
$reference = "foo->bar";
echo $xml->$reference;
?>
That won't work. Any solution?

It's because you are trying to access 3rd level in one variable. PHP couldn't handle -> in variable.
$level1 = 'foo';
$level2 = 'bar';
echo $xml->$level1->$level2;

You can't do that because it will look for a property with the literal foo->bar, and not bar inside foo.
You could do it like this:
$xml = simplexml_load_file("myxml.xml");
$reference = "foo->bar";
$tmp = $xml;
foreach(explode('->', $reference) as $v){
$tmp = $tmp->$v;
}
echo $tmp;
Output:
Hello, World!
This would work even if you don't want to go until the last element. Take a look at the following example.
Test with this XML:
<?xml version="1.0" encoding="utf-8"?>
<test>
<foo>
<bar>Hello, World!</bar>
<something>
<values>
<v1>Some value here (1)</v1>
<v2>Some value here (2)</v2>
<v3>Some value here (3)</v3>
<v4>Some value here (4)</v4>
</values>
</something>
</foo>
</test>
Now, change to $reference = "foo->something->values"; and from echo $tmp; to print_r($tmp);. This will be the output:
SimpleXMLElement Object
(
[v1] => Some value here (1)
[v2] => Some value here (2)
[v3] => Some value here (3)
[v4] => Some value here (4)
)

Use Xpath. It allows you to use expressions to fetch parts of an XML.
$xml = <<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<test>
<foo>
<bar>Hello, World!</bar>
</foo>
</test>
XML;
$xml = new SimpleXMLElement($xml);
$expression = 'foo/bar';
var_dump(
(string)$xml->xpath($expression)[0]
);
Xpath is a really powerful tool. However to use the full potential you will have to use DOMXpath::evaluate(). SimpleXMLElement::xpath() can only return node lists as arrays of SimpleXMLElement objects. DOMXpath:evaluate() can return node lists or scalar values.
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$expression = 'string(foo/bar)';
var_dump(
$xpath->evaluate($expression)
);

Related

xpath with simplexml not able to access nested element

I have an xml like so:
<?xml version="1.0" encoding="UTF-8"?>
<foo SeqNum="1">
<bar>1234</bar>
</foo>
<foo SeqNum="20">
<bar>6789</bar>
</foo>
and I'm trying to get the value 6789 with this query:
$xml = "<?xml version="1.0" encoding="UTF-8"?>
<foo SeqNum="1">
<bar>1234</bar>
</foo>
<foo SeqNum="20">
<bar>6789</bar>
</foo>";
$simple = new SimpleXMLElement($xml);
$result = $simple->xpath('//*[#SeqNum="20"]/bar/'); // result gives me nothing
So I tried to just get the parent like so
$result = $simple->xpath('//*[#SeqNum="20"]')[0]->asXML();
which gives me:
<foo SeqNum="20">
<bar>6789</bar>
</foo>
So I'm almost there but am really stuck about what I'm not understanding. Thank you!
Here are several mistakes in the question. The XML needs a root element and the trailing / breaks the expression. The literal quotes need to be changed to single quotes (or all the inner double quotes need to be escaped.)
Fixed example:
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<foo>
<foo SeqNum="1">
<bar>1234</bar>
</foo>
<foo SeqNum="20">
<bar>6789</bar>
</foo>
</foo>';
$simple = new SimpleXMLElement($xml);
$result = $simple->xpath('//*[#SeqNum="20"]/bar');
var_dump((string)$result[0]);
Output:
string(4) "6789"
With Namespaces
If your XML is using namespaces you will have to define an alias/prefix for this namespace URI and use that in the Xpath expression.
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8" ?>
<p:foo xmlns:p="http://www.example.com">
<p:foo SeqNum="1">
<p:bar>1234</p:bar>
</p:foo>
<p:foo SeqNum="20">
<p:bar>6789</p:bar>
</p:foo>
</p:foo>
XML;
$simple = new SimpleXMLElement($xml);
$simple->registerXpathNamespace('e', 'http://www.example.com');
$result = $simple->xpath('//*[#SeqNum="20"]/e:bar');
var_dump((string)$result[0]);
The example uses a different alias for the expression to show that the document and the expression are separate - only the namespace URI has to match.
Namespaces have to be unique so they are defined with an URI (a superset of URL). Because that would get messy aliases are used in node names. The following 3 elements all can be read as {http://www.example.com}bar.
<p:bar xmlns:p="http://www.example.com"/>
<e:bar xmlns:e="http://www.example.com"/>
<bar xmlns="http://www.example.com"/>

PHP SimpleXMLElement problems with xml node

I want to create an XML with the following structure:
<?xml version="1.0" encoding="UTF-8"?>
<content>
<!-- content goes here -->
</content>
I originally created the xml node like this:
$xml = new SimpleXMLElement('<xml/>');
$content = $xml->addChild('content');
// add data to content
but that doesn't allow for adding attributes to the xml node, so now I do this:
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>'
.'<content></content>');
For some reason it doesn't work without adding the content node, but whatever, it gets the structure right.
Now, how do I assign the content node to a variable like I did above, so I can add data to it?
In your case the $xml variable is equal to the content node just try the following:
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>'
.'<content></content>');
$xml->addAttribute('Attribute', 'value');
$xml->addChild('node_name', 'value');
echo $xml->asXML();
this should print
<?xml version="1.0" encoding="UTF-8"?>
<content Attribute="value"><node_name>value</node_name></content>
E.g.
<?php
$content = new SimpleXMLElement('<content />');
$content['attr']='value';
echo $content->asXML();
prints
<?xml version="1.0"?>
<content attr="value"/>
--- edit:
To keep the encoding=utf-8:
$content = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>
<content />');
An XML document must have at least one element. That is the document element. In your question this is the content element.
You can create a SimpleXMLElement of it by just instantiating it with this minimum string:
$xml = new SimpleXMLElement('<content/>');
The variable $xml then represents that element. You can then...
... add attributes: $xml['attribute'] = 'value';
... set the content-text: $xml[0] = 'text';
... add child-elements: $xml->child = 'value';
This exemplary line-up then would have created the following XML (beautified, also: online demo):
<?xml version="1.0"?>
<content attribute="value">
text
<child>value</child>
</content>

easy xpath query but no results

Trying to get all URLs values from xml.
I have hundreds of entry exactly in the form like e.g. this entry 16:
<?xml version="1.0" encoding="utf-8" ?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<entries>
<entry id="16">
<revision number="1" status="accepted" wordclass="v" nounclasses="" unverified="false"></revision>
<media type="audio" url="http://website.com/file/65.mp3" />
</entry>
<entry id="17">
....
</entry>
</entries>
</root>
I am using this code but cannot get it to work. Why?
$doc = new DOMDocument;
$doc->Load('data.xml');
$xpath = new DOMXPath($doc);
$query = '//root/entries/entry/media';
$entries = $xpath->query($query);
What is the correc query for that? Best would be to only get the url value.
Your query probably returns the proper elements, but by default gives you the content of the media tag ( which in your case are empty, since the tag is self-closing ).
To get the url attribute of the tag you should use getAttribute(), example :
$entries = $xpath->query('//root/entries/entry/media');
foreach($entries as $entry) {
print $entry->getAttribute("url")."<br/>";
}
Or you should just xpath-query the attribute instead and read out it's value:
$urlAttributes = $xpath->query('//root/entries/entry/media/#url');
#####
foreach ($urlAttributes as $urlAttribute)
{
echo $urlAttribute->value, "<br/>\n";
#####
}
See DOMAttr::$valueDocs:
value
The value of the attribute
I would do that with SimpleXML actually:
$file = 'data.xml';
$xpath = '//root/entries/entry/media/#url';
$xml = simplexml_load_file($file);
$urls = array();
if ($xml) {
$urls = array_map('strval', $xml->xpath($xpath));
}
Which will give you all URLs as strings inside the $urls array. If there was an error loading the XML file, the array is empty.

How to add new Child to SimpleXML when one with same name already exists?

I'm trying to add a child to an Simple XML object, but when an element with the same name already exists on that level it doesn't get added.
Here's what I'm trying:
$str = '<?xml version="1.0"?>
<root>
<items>
<item></item>
</items>
</root>';
$xml = new SimpleXMLElement($str);
$xml->addChild('items');
print $xml->asXML();
I get the exact same xml as I started with, when what I really want is a second empty items element. If I use another element name than it does get added.
Use this code for adding a new items node in your example:
$str = '<?xml version="1.0"?>
<root>
<items>
<item></item>
</items>
</root>';
$xml = new SimpleXMLElement($str);
$xml->addChild('items', '');
var_dump($xml->asXML());
Which outputs:
string '<?xml version="1.0"?>
<root>
<items>
<item/>
</items>
<items></items></root>
' (length=109)
You could use simpleloadxml as alternate
$xml = simplexml_load_file("myxml.xml");
$sxe = new SimpleXMLElement($xml->asXML());
$itemsNode = $sxe->items[0];
$itemsNode->addChild("item", $newValue);
$sxe->asXML("myxml.xml");

Is it possible to change a xml variable by only its tag name with simplexml in php

I know I can set a variable like that $xml->path->to->tag = $newValue . But what if I have only tag name and don't know its path, I wonder how I can set its variable? is it possible?
You are looking for
SimpleXml::xpath() - searches the SimpleXML node for children matching the XPath path.
The XPath to search for an element anywhere in the XML document is //elementName
Example XML:
<foo>
<bar>
<baz bam="boom">baddam</baz>
</bar>
</foo>
Example PHP Code:
$foo = simplexml_load_string($xml);
$allBazElements = $foo->xpath('//baz');
echo
$allBazElements[0], // baddam
$allBazElements[0]['bam'], // boom
PHP_EOL;
$allBazElements[0][0] = 'changed';
$allBazElements[0]['bam'] = 'changed too';
echo $foo->asXml();
will output (demo)
baddamboom
<?xml version="1.0"?>
<foo>
<bar>
<baz bam="changed too">changed</baz>
</bar>
</foo>

Categories