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'
Related
I am using xsd2php library to parse XSD which describes API request body. Then using the same library (which itself uses jsm-serializer) I try to serialize objects:
$payload = new TrackRequest;
$searchCriteria = new SearchCriteriaAType;
$searchCriteria->addToConsignmentNumber(11111);
$payload->setSearchCriteria($searchCriteria);
$levelOfDetail = new LevelOfDetailAType;
$levelOfDetail->setSummary(true);
$payload->setLevelOfDetail($levelOfDetail);
Using basic serializer settings:
$serializerBuilder = SerializerBuilder::create();
$serializerBuilder->addMetadataDir(__DIR__ . '/../../metadata/Tracking', 'TNTExpressConnect\Tracking\XSD');
$serializerBuilder->setPropertyNamingStrategy(new IdenticalPropertyNamingStrategy);
$serializerBuilder->configureHandlers(function (HandlerRegistryInterface $handler) use ($serializerBuilder) {
$serializerBuilder->addDefaultHandlers();
$handler->registerSubscribingHandler(new BaseTypesHandler()); // XMLSchema List handling
$handler->registerSubscribingHandler(new XmlSchemaDateHandler()); // XMLSchema date handling
});
Serialization results in:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<searchCriteria>
<account/>
<alternativeConsignmentNumber/>
<consignmentNumber>
<entry><![CDATA[11111]]></entry>
</consignmentNumber>
<customerReference/>
<pieceReference/>
</searchCriteria>
<levelOfDetail>
<summary>true</summary>
</levelOfDetail>
</result>
Regarding this results I have several questions:
Why the root element is <result> and not <TrackRequest>?
How to get rid of CDATA?
How to get rid of <entry> tags in favor of creating separate consigmentNumber tag for each entry?
How to replace <summary>true</summary> with self-closing tag <summary/>
I guess for every one of this cases I can create a dedicated handler, but maybe there is a built-in solution, which I overlooked in the documentation (maybe some config options that can be placed in yaml).
And if I have to create handlers maybe someone can point me the more sophisticated example, that explains how to do it right.
I'm not a big fan of annotations, so I would prefer to use separate config files.
Thank you in advance.
You should have a look ar the YAML Reference. A lot of things can be set up with the meta data files.
To change the "result" to "TrackRequest" add this line to the file:
Vendor\MyBundle\Model\ClassName:
xml_root_name: TrackRequest ## Changes the root element
To get rid of cdata in entry change the property:
properties:
entry:
xml_element:
cdata: false ## Add this to disable cdata tags
Just came accross the same problems as you did. I hope it helps.
I am learning SimpleXML in PHP. Then I am doing simple test with SimpleXMLElement(...), I dont get anything back. Let me explain. Here is XML file:
<?xml version="1.0" encoding="UTF-8"?>
<movies>
<movie>
<title>PHP: Behind the Parser</title>
<plot>
So, this language. It's like, a programming language. Or is it a
scripting language? All is revealed in this thrilling horror spoof
of a documentary.
</plot>
<great-lines>
<line>PHP solves all my web problems</line>
</great-lines>
<rating type="thumbs">7</rating>
<rating type="stars">5</rating>
</movie>
</movies>
And here is my php file:
<?php
$xml = simplexml_load_file('example.xml');
echo $xml->getName() . "<br>"; // prints "movies"
$movies = new SimpleXMLElement($xml);
echo $movies->getName() . "...<br>"; // doesnt print anything, not event dots
echo $movies->movie[0]->plot; // even this does not print anything
?>
Only output is:
movies
Please read the comments in php file. I am trying to print xml elements in exact same way after loading file and after doing new simpleXML object. Some how it prints only first echo command results. I searched many examples and could not make it work. Where is the mistake? It is big puzzle for me, but maybe a tiny one for you.
simplexml_load_file already returns your SimpleXMLElement object. Try this:
<?php
$xml = simplexml_load_file('example.xml');
echo $xml->getName() . "<br>";
echo $xml->movie[0]->plot . "<br>\n";
?>
change this line:
$movies = new SimpleXMLElement($xml);
to this:
$movies = new SimpleXMLElement($xml->asXML());
What you are trying to do doesn't make much sense, because you are trying to load the same XML twice:
// this loads the XML from a file, giving you a SimpleXMLElement object:
$xml = simplexml_load_file('example.xml');
// this line would do what? load the XML from the XML?
$movies = new SimpleXMLElement($xml);
There are two functions for loading XML in the SimpleXML extension, both return SimpleXMLElement objects:
simplexml_load_file - takes a filename, and loads the XML in that file; with the right PHP settings, you can also give it a URL, and it will load the XML straight from there
simplexml_load_string - takes a string of XML that you've already got from somewhere else, and loads that
The third way of getting a SimpleXMLElement is calling the class's constructor (i.e. writing new SimpleXMLElement). This can actually act like either of the above: by default, it expects a string of XML (like simplexml_load_string), but you can also set the 3rd parameter to true to say that it's a path or URL (like simplexml_load_file).
The result of all three of these methods is exactly the same, they're just different ways of getting there depending on what you currently have (and, to some extent, how you want your code to look).
As a side-note, there are two more functions which do take an object of XML you've already parsed: simplexml_import_dom and dom_import_simplexml. These are actually pretty cool, because the DOM is a standard, comprehensive, but rather fiddly and verbose way of acting on XML, whereas SimpleXML is, well, simple - and using these functions you can actually use both with very little penalty, because they just change the wrapper of the object without having to re-parse the underlying XML.
try this
<?php
$movies = simplexml_load_file('sample.xml');
foreach($movies as $key=>$val)
{
echo $val->title.'<br>';
echo $val->plot.'<br>';
echo $val->rating[0];
echo $val->rating[1];
}
?>
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.
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');
Quick newbie question here, how do I access totalResults?
XML
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
<opensearch:totalResults>1</opensearch:totalResults>
<posts>
<post>
<score>10</score>
</post>
</posts>
</OpenSearchDescription>
To access the score I would do this:
PHP
$xmlObj = simplexml_load_string($theXMLabove);
echo $xmlObj->posts->post[0]->score;
But none of these work for the totalResults:
echo $xmlObj->opensearch:totalResults;
echo $xmlObj->opensearch->totalResults;
Sorry for asking such a lame question...
Documentation on how to traverse XML with PHP is also appreciated :)
Thanks!
with the namespace added you can do this:
$opensearch = $xmlObj->children('http://a9.com/-/spec/opensearch/1.1/');
echo $opensearch->totalResult;
try: $xmlObj->children('opensearch');
Im not sure if that will work though because from what you posted the opensearch namespace isnt defined as an xmlns. That might not make a difference though - im not sure because when ive had to deal with ns in simplexml the ns has always been explicitly defined.