Parsing Zimbra SOAP response with SimpleXML and xpath - php

So, I'm using PHP to talk to a Zimbra SOAP server. The response is in a <soap:Envelope> tag. I'm having trouble parsing the XML response because of the namespace(s).
The XML looks like this:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<context xmlns="urn:zimbra">
<change token="20333"/>
</context>
</soap:Header>
<soap:Body>
<CreateAccountResponse xmlns="urn:zimbraAdmin">
<account id="83ebf344-dc51-47ae-9a36-3eb24281d53e" name="iamtesting#example.com">
<a n="zimbraId">83ebf344-dc51-47ae-9a36-3eb24281d53e</a>
<a n="zimbraMailDeliveryAddress">iamtesting#example.com</a>
</account>
</CreateAccountResponse>
</soap:Body>
</soap:Envelope>
I make a new SimpleXMLElement object:
$xml = new SimpleXMLElement($data);
After Googling a bit, I found I need to register the namespace. So I do that:
$xml->registerXPathNamespace('soap', 'http://www.w3.org/2003/05/soap-envelope');
Then I can get the <soap:Body> tag easily.
$body = $xml->xpath('//soap:Body');
But I can't get any elements after that (using xpath):
$CreateAccountResponse = $xml->xpath('//soap:Body/CreateAccountResponse');
This returns an empty array. I can traverse the XML though, to get that element.
$CreateAccountResponse = $body[0]->CreateAccountResponse;
This works fine, but now I want to get the <a> tags, specifically the zimbraId one. So I tried this:
$zimbraId = $CreateAccountResponse->account->xpath('a[#n=zimbraId]');
No luck, I get a blank array. What's going on? Why can't I use xpath to get elements (that don't start with soap:)?
How can I get the <a> tags based on their n attribute?
P.S. I'm aware that the id and name are also in the <account> tag's attributes, but there are a bunch more <a> tags that I want to get using the n attribute.
Note: I'm trying to improve the Zimbra library for my application for work. The current code to get the <a> tags is as follows:
$zimbraId = strstr($data, "<a n=\"zimbraId\"");
$zimbraId = strstr($zimbraId, ">");
$zimbraId = substr($zimbraId, 1, strpos($zimbraId, "<") - 1);
Obviously, I want to remove this code (there's also some regexes (shudder) later on in the code), and use an XML parser.

The elements you want to retrieve have a namespace as well, namely urn:zimbraAdmin.
<CreateAccountResponse xmlns="urn:zimbraAdmin">
The xmlns attribute states the default namespace for any child elements, so the elements you are trying to retrieve actually have a namespace, even though no prefix is used (see the wikipedia article for some examples). If you specify a namespace prefix as you did for http://www.w3.org/2003/05/soap-envelope you should be fine.
$xml->registerXPathNamespace('soap', 'http://www.w3.org/2003/05/soap-envelope');
$xml->registerXPathNamespace('zimbra', 'urn:zimbraAdmin');
$CreateAccountResponse = $xml->xpath('//soap:Body/zimbra:CreateAccountResponse');

Related

Issue appending to xml from html using SimpleXML

I am trying to do a simple append to an existing XML document using SimpleXML in PHP. I have an HTML form that calls a PHP script that tries to append data to an already existing XML document in the same directory.
I have tried the basic implementation of this, but I keep getting the follow error:
Warning: SimpleXMLElement::addChild(): Cannot add child. Parent is
not a permanent member of the XML tree in
/the/directory/to/the/site/generate.php on line
9
Fatal error: Uncaught Error: Call to a member function addChild() on
null...
Here is the already existing XML file that is being used:
<?xml version="1.0" encoding="UTF-8"?>
<tasks>
<users/>
<taskList>
<tasks id="1234">
<activities>
<activity/>
</activities>
</task>
</taskList>
</tasks>
Here is the PHP code:
<?php
$file = 'tasks.xml';
$xml = simplexml_load_file($file);
$activities = $xml->activities;
$activity = $activities->addChild('activity');
$activity->setAttribute('id', '45678');
$activity->addChild('assigned', 'Jon');
$activity->addChild('priority', 'low');
$xml->asXML($file);
I am hard coding the values in just to get the append to work, but eventually these values will be from a submitted html form.
Any ideas as to why this is failing?
There are a couple of problems with your code. Firstly your XML is invalid, your close tag should be </tasks>.
When you try and get the <activities> tag in
$activities = $xml->activities;
this is trying to find the tag just off the root of the document, you could use the full path
$activities = $xml->taskList->tasks->activities;
or use (I have in this code) XPath to find it, this also allows you to pick out (if neccessary) which <tasks> tag you use depending of id.
$activities = $xml->xpath("//activities")[0];
$activity = $activities->addChild('activity');
$activity->addAttribute('id', '45678');
$activity->addChild('assigned', 'Jon');
$activity->addChild('priority', 'low');
You also use setAttribute() which doesn't exist - as the code shows it is addAttribute().

PHP - Dynamically append web page to sitemap.xml

I have a site that searches through a database of websites. I am trying to get to append an url to sitemap.xml whenever the search returns a single result. I know I would need to use simplexml but I'm not sure how to implement it.
Pseudo Code
if (correct results) {
$theDate = date('c',time());
$theUrl = "http://myurl.com/?r=asdfasdf1234"
appendtositemap($theDate, $theUrl);
}
Current sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://example.com/</loc>
<lastmod>2013-04-08T20:38:15+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
</urlset>
You would first want to parse what you already have, if you're adding on to it:
$sitemap = simplexml_load_string(YOUR_ORIGINAL_XML_HERE_AS_A_STRING);
After that, let's say you want to create a new url node. You can do this by using the addChild function on any SimpleXMLElement (this creates a child XML node, or the nested node structure you see above):
$myNewUri = $sitemap->addChild("url");
$myNewUri->addChild("loc", "http://www.google.com/");
$myNewUri->addChild("lastmod", "2015-01-07T20:50:10+00:00");
$myNewUri->addChild("changefreq", "daily");
$myNewUri->addChild("priority", "2.0");
Here, the first property is always required; that's the name of the XML node that you're adding. The second parameter is optional, but it specifies the text value to add to the new node. Do this for each of the links and you'll keep appending nodes
Finally, you want to print it out, no? To do that, use:
echo $sitemap->asXml();
If you want to save it to a file instead:
$sitemap->asXml("sitemap.xml");

SimpleXML parent - child issue

i am having an issue with parsing an XML file using SimpleXML and PHP.
The XML file in question is provided by a third party and includes a number of child elements (going down multiple levels) within it. I know which elements i require and can see them within the XML file, but i just can't seem to get them to print using PHP.
Example XML feed for test.xml:
<?xml version="1.0" encoding="utf-8"?>
<Element1 xmlns="" release="8.1" environment="Production" lang="en-US">
<Element2>
<Element3>
<Element4>
<Element5>it worked</Element5>
</Element4>
</Element3>
</Element2>
</Element1>
The file only includes one of each attribute so i can be very particular with the request, the code i have so far is below:
$lib=simplexml_load_file("test.xml");
$make=$lib->Element1->Element2->Element3->Element4->Element5;
print $make;
I have tried to look this up before asking, but the only solutions i can see are when the child attributes are unknown or there are multiple results for each request, which is not the case in this instance.
Any help or guidance would be greatly received.
Thanks
In your code above, $lib is Element1. So you just need to drop one of your references. This:
$make=$lib->Element1->Element2->Element3->Element4->Element5;
Should become this:
$make=$lib->Element2->Element3->Element4->Element5;
Also, SimpleXML is an awful awful awful awful interface (considering that "Simple" is in the name and there is mass confusion about how to use it). I would always recommend DOMDocument instead.
I'd strongly recommend using xpath as it will give you more flexibility e.g. Allow you to restrict results based on xml node attributes.
$xml = simplexml_load_string('<?xml version="1.0" encoding="utf-8"?>
<Element1 xmlns="" release="8.1" environment="Production" lang="en-US">
<Element2>
<Element3>
<Element4>
<Element5>it worked</Element5>
</Element4>
</Element3>
</Element2>
</Element1>');
$data=$xml->xpath('/Element1/Element2/Element3/Element4/Element5');
echo (string)$data[0]; //outputs 'it worked'
//this also works
$data=$xml->xpath('//Element5');
echo (string)$data[0]; //outputs 'it worked'

Can't access XML node via xpath() (YT channel feed)

Very stumped by this one. In PHP, I'm fetching a YouTube user's vids feed and trying to access the nodes, like so:
$url = 'http://gdata.youtube.com/feeds/api/users/HCAFCOfficial/uploads';
$xml = simplexml_load_file($url);
So far, so fine. Really basic stuff. I can see the data comes back by running:
echo '<p>Found '.count($xml->xpath('*')).' nodes.</p>'; //41
echo '<textarea>';print_r($xml);echo '</textarea>';
Both print what I would expect, and the print_r replicates the XML structure.
However, I have no idea why this is returning zero:
echo '<p>Found '.count($xml->xpath('entry')).'"entry" nodes.</p>';
There blatantly are entry nodes in the XML. This is confirmed by running:
foreach($xml->xpath('*') as $node) echo '<p>['.$node->getName().']</p>';
...which duly outputs "[entry]" 25 times. So perhaps this is a bug in SimpleXML? This is part of a wider feed caching system and I'm not having any trouble with other, non-YT feeds, only YT ones.
[UPDATE]
This question shows that it works if you do
count($xml->entry)
But I'm curious as to why count($xml->xpath('entry')) doesn't also work...
[Update 2]
I can happily traverse YT's anternate feed format just fine:
http://gdata.youtube.com/feeds/base/users/{user id}/uploads?alt=rss&v=2
This is happening because the feed is an Atom document with a defined default namespace.
<feed xmlns="http://www.w3.org/2005/Atom" ...
Since a namespace is defined, you have to define it for your xpath call too. Doing something like this works:
$url = 'http://gdata.youtube.com/feeds/api/users/HCAFCOfficial/uploads';
$xml = simplexml_load_file($url);
$xml->registerXPathNamespace('ns', 'http://www.w3.org/2005/Atom');
$results = $xml->xpath('ns:entry');
echo count($results);
The main thing to know here is that SimpleXML respects any and all defined namespaces and you need to handle them accordingly, including the default namespace. You'll notice that the second feed you listed does not define a default namespace and so the xpath call works fine as is.

XML Namespaces with PHP's xmlwriter

Firstly can you tell me whether this xml:
<adf:source xsi:schemaLocation="http://www.rightmove.co.uk/adf/rightmoveV4n.xsd rightmoveV4n.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:adf="http://www.rightmove.co.uk/adf/rightmoveV4n.xsd">
</source>
Is correct? I can't see how the document starts with: <adf: source> and closes with </source>, doesn't seem right to me?
I have replicated the structure using my own data but cannot get PHP's XMLWriter() to close the document with just </source> - it closes it with </adf:source>.
I'm doing:
$xml = new XMLWriter();
$xml->openMemory();
$xml->startDocument();
$xml->startElementNS("adf", "source", "http://www.rightmove.co.uk/adf/rightmoveV4n.xsd");
$xml->writeAttributeNS ("xsi", "schemaLocation", "http://www.w3.org/2001/XMLSchema-instance", "http://www.rightmove.co.uk/adf/rightmoveV4n.xsd rightmoveV4n.xsd");
and then eventually
$xml->endElement ();
echo $xml->outputMemory();
No, your XML is not well-formed. The root node of an XML document must be opened and closed with the same element. As far as an XML parser is concerned, <adf:source> and <source> are entirely different.
The adf: in front of the source element is a so-called namespace prefix, which is like a shorthand way of saying: "This element belongs to the namespace http://www.rightmove.co.uk/adf/rightmoveV4n.xsd".
So, the behaviour of XMLWriter() is to be expected and perfectly fine. On the other hand, an application that produces the XML document you have shown is clearly in error.

Categories