xpath with simplexml not able to access nested element - php

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

Related

php SimpleXML get full namespace attributes [duplicate]

I'd like to get the content of the attribute xsi:schemaLocation. It's works perfectly with getElementsByTagName in php (and foreach after) but it's ugly, right ?
How to get the same content with a simple Xpath query ?
Here a short example of the xml content :
<?xml version="1.0" encoding="utf-8"?>
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1.0" creator="blabla" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.groundspeak.com/cache/1/0/1 http://www.groundspeak.com/cache/1/0/1/cache.xsd" xmlns="http://www.topografix.com/GPX/1/0">
...
</gpx>
Thanks!
Typically you need to register the namespaces you want to use with the XPath library first. Then you can query the attribute by including namespace prefix along with the name.
So let's assume you're using DOMXPath, you might register the following namespaces:
$xpath = new DOMXPath($doc);
$xpath->registerNamespace("xsi","http://www.w3.org/2001/XMLSchema-instance");
$xpath->registerNamespace("gpx", "http://www.topografix.com/GPX/1/0");
And then you can query the schemaLocation attribute with something like this:
$xpath->query("/gpx:gpx/#xsi:schemaLocation",$doc);
Using the SimpleXMLElement class you can easily get the attribute xsi:schemaLocation's value:
<?php
$xml = <<<XML
<?xml version="1.0" encoding="utf-8"?>
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1.0" creator="blabla" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.groundspeak.com/cache/1/0/1 http://www.groundspeak.com/cache/1/0/1/cache.xsd" xmlns="http://www.topografix.com/GPX/1/0">
</gpx>
XML;
$sxe = new SimpleXMLElement($xml);
$schemaLocation = $sxe->attributes('xsi', true)->schemaLocation;
echo (string) $schemaLocation;

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>

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>

Insert an xml element from an xml doc to an other xml document

Let's say I have to xml documents:
<first_root>
<element1/>
<element2>
<embedded_element/>
</element2>
</first_root>
and
<foo>
<bar/>
</foo>
How can I put this second xml doc into the first one, using php and DomDocument or SimpleXML?
I want it to look like something like this:
<first_root>
<element1/>
<element2>
<foo>
<bar/>
</foo>
<embedded_element/>
</element2>
</first_root>
You can do it using DOMDocument:
<?php
$aDoc = DOMDocument::loadXML('<?xml version="1.0" encoding="UTF-8" ?>
<first_root>
<element1/>
<element2>
<embedded_element/>
</element2>
</first_root>');
$bDoc = DOMDocument::loadXML('<?xml version="1.0" encoding="UTF-8" ?>
<foo>
<bar/>
</foo>');
$aEmbeddedElement = $aDoc->getElementsByTagName('embedded_element')->item(0);
$bFoo = $bDoc->documentElement;
$aImportedFoo = $aDoc->importNode($bFoo,true);
$aEmbeddedElement->insertBefore($aImportedFoo);
echo $aDoc->saveXML();
?>
Here I imported the XML into DOMDocuments, then I picked up the first embedded_element occurrence and foo node. After you have to import deeply foo into the first document. Now you can insert foo before embedded_element.
Obviously this is only the happy case...
Documentation: DOM
With SimpleXML you can accomplish this by building a third document based on the first two because you can't append SimpleXMLElements into others. (Or maybe you can but there's something I didn't get)

Categories