"I found simpleXML to be useful only in cases where the XML was extremely small, otherwise the server would run out of memory (I suspect there is a memory leak or something?)."
This appears for me in PHP 5.1.6 cli when you 'foreach' over elements.
Use a 'for' loop instead.
SimpleXML functions
Úvod
The SimpleXML extension provides a very simple and easily usable toolset to convert XML to an object that can be processed with normal property selectors and array iterators.
Požiadavky
The SimpleXML extension requires PHP 5.
Inštalácia
The SimpleXML extension is enabled by default. To disable it, use the --disable-simplexml configure option.
Príklady
Many examples in this reference require an XML string. Instead of repeating this string in every example, we put it into a file which we include in each example. This included file is shown in the following example section. Alternatively, you could create an XML document and read it with simplexml_load_file().
Example#1 Include file example.php with XML string
<?php
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<movies>
<movie>
<title>PHP: Behind the Parser</title>
<characters>
<character>
<name>Ms. Coder</name>
<actor>Onlivia Actora</actor>
</character>
<character>
<name>Mr. Coder</name>
<actor>El ActÓr</actor>
</character>
</characters>
<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>
XML;
?>
The simplicity of SimpleXML appears most clearly when one extracts a string or number from a basic XML document.
Example#2 Getting <plot>
<?php
include 'example.php';
$xml = new SimpleXMLElement($xmlstr);
echo $xml->movie[0]->plot; // "So this language. It's like..."
?>
Accessing elements within an XML document that contain characters not permitted under PHP's naming convention (e.g. the hyphen) can be accomplished by encapsulating the element name within braces and the apostrophe.
Example#3 Getting <line>
<?php
include 'example.php';
$xml = new SimpleXMLElement($xmlstr);
echo $xml->movie->{'great-lines'}->line; // "PHP solves all my web problems"
?>
Example#4 Accessing non-unique elements in SimpleXML
When multiple instances of an element exist as children of a single parent element, normal iteration techniques apply.
<?php
include 'example.php';
$xml = new SimpleXMLElement($xmlstr);
/* For each <movie> node, we echo a separate <plot>. */
foreach ($xml->movie as $movie) {
echo $movie->plot, '<br />';
}
?>
Example#5 Using attributes
So far, we have only covered the work of reading element names and their values. SimpleXML can also access element attributes. Access attributes of an element just as you would elements of an array.
<?php
include 'example.php';
$xml = new SimpleXMLElement($xmlstr);
/* Access the <rating> nodes of the first movie.
* Output the rating scale, too. */
foreach ($xml->movie[0]->rating as $rating) {
switch((string) $rating['type']) { // Get attributes as element indices
case 'thumbs':
echo $rating, ' thumbs up';
break;
case 'stars':
echo $rating, ' stars';
break;
}
}
?>
Example#6 Comparing Elements and Attributes with Text
To compare an element or attribute with a string or pass it into a function that requires a string, you must cast it to a string using (string). Otherwise, PHP treats the element as an object.
<?php
include 'example.php';
$xml = new SimpleXMLElement($xmlstr);
if ((string) $xml->movie->title == 'PHP: Behind the Parser') {
print 'My favorite movie.';
}
htmlentities((string) $xml->movie->title);
?>
Example#7 Using XPath
SimpleXML includes built-in XPath support. To find all <character> elements:
<?php
include 'example.php';
$xml = new SimpleXMLElement($xmlstr);
foreach ($xml->xpath('//character') as $character) {
echo $character->name, 'played by ', $character->actor, '<br />';
}
?>
'//' serves as a wildcard. To specify absolute paths, omit one of the slashes.
Example#8 Setting values
Data in SimpleXML doesn't have to be constant. The object allows for manipulation of all of its elements.
<?php
include 'example.php';
$xml = new SimpleXMLElement($xmlstr);
$xml->movie[0]->characters->character[0]->name = 'Miss Coder';
echo $xml->asXML();
?>
The above code will output a new XML document, just like the original, except that the new XML will change Ms. Coder to Miss Coder.
Example#9 Adding elements and attributes
Since PHP 5.1.3, SimpleXML has had the ability to easily add children and attributes.
<?php
include 'example.php';
$xml = new SimpleXMLElement($xmlstr);
$character = $xml->movie[0]->characters->addChild('character');
$character->addChild('name', 'Mr. Parser');
$character->addChild('actor', 'John Doe');
$rating = $xml->movie[0]->addChild('rating', 'PG');
$rating->addAttribute('type', 'mpaa');
echo $xml->asXML();
?>
The above code will output an XML document based on the original but having a new character and rating.
Example#10 DOM Interoperability
PHP has a mechanism to convert XML nodes between SimpleXML and DOM formats. This example shows how one might change a DOM element to SimpleXML.
<?php
$dom = new domDocument;
$dom->loadXML('<books><book><title>blah</title></book></books>');
if (!$dom) {
echo 'Error while parsing the document';
exit;
}
$s = simplexml_import_dom($dom);
echo $s->book[0]->title;
?>
Table of Contents
- SimpleXMLElement->addAttribute() — Adds an attribute to the SimpleXML element
- SimpleXMLElement->addChild() — Adds a child element to the XML node
- SimpleXMLElement->asXML() — Return a well-formed XML string based on SimpleXML element
- SimpleXMLElement->attributes() — Identifies an element's attributes
- SimpleXMLElement->children() — Finds children of given node
- SimpleXMLElement->__construct() — Creates a new SimpleXMLElement object
- SimpleXMLElement->getDocNamespaces() — Returns namespaces declared in document
- SimpleXMLElement->getName() — Gets the name of the XML element
- SimpleXMLElement->getNamespaces() — Returns namespaces used in document
- SimpleXMLElement->registerXPathNamespace() — Creates a prefix/ns context for the next XPath query
- SimpleXMLElement->xpath() — Runs XPath query on XML data
- simplexml_import_dom — Get a SimpleXMLElement object from a DOM node.
- simplexml_load_file — Interprets an XML file into an object
- simplexml_load_string — Interprets a string of XML into an object
SimpleXML
14-Sep-2008 07:35
02-Sep-2008 02:08
If you want CDATA in your object you should use LIBXML_NOCDATA
<?php
$xml = simplexml_load_file($file_xml, 'SimpleXMLElement',LIBXML_NOCDATA);
print_r($xml);
?>
28-Aug-2008 06:55
While SimpleXML is a powerful tool, developers using it to process and handle large XML documents & strings should take into careful consideration its memory usage requirements.
SimpleXML requires the entire XML tree to be available prior to any processing actions on that tree -- this requires the entire tree to be in memory. Ok for a 40kb XML file but when dealing with > 100MB files you will see performance degradation, especially if you have a busy server.
An alternative to processing large XML files is the XMLReader class, which operates in streaming mode, of which an excellent tutorial is presented here:
http://www.ibm.com/developerworks/library/x-pullparsingphp.html
XML is an incredibly verbose format. If you are dealing with large data structures, especially in web services, do you you actually need to represent the data as an XML tree? - other serialisation formats such as JSON, Serialised PHP, Google Protocol Buffers (http://code.google.com/p/protobuf/) and even CSV can remarkably reduce processing time, bandwidth and load when dealing with large files. A 110MB XML file can become a 65MB CSV file, with the same data in it, simply because the data identifiers (tags) are only represented once in the entire document.
25-Jul-2008 08:33
For clarification, finding attributes seems easier this way, hope I'm not being redundant.
Source XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body type="small" important="low">Don't forget me this weekend!</body>
</note>
Code Example:
<?php
$xml = simplexml_load_file("test.xml");
print $xml->body['type'];
?>
Output:
small
10-Jul-2008 07:07
There seems to be a lot of talk about SimpleXML having a "problem" with CDATA, and writing functions to rip it out, etc. I thought so too, at first, but it's actually behaving just fine under PHP 5.2.6
The key is noted above example #6 here:
http://uk2.php.net/manual/en/simplexml.examples.php
"To compare an element or attribute with a string or pass it into a function that requires a string, you must cast it to a string using (string). Otherwise, PHP treats the element as an object."
If a tag contains CDATA, SimpleXML remembers that fact, by representing it separately from the string content of the element. So some functions, including print_r(), might not show what you expect. But if you explicitly cast to a string, you get the whole content.
<?php
$xml = simplexml_load_string('<foo>Text1 & XML entities</foo>');
print_r($xml);
/*
SimpleXMLElement Object
(
[0] => Text1 & XML entities
)
*/
$xml2 = simplexml_load_string('<foo><![CDATA[Text2 & raw data]]></foo>');
print_r($xml2);
/*
SimpleXMLElement Object
(
)
*/
// Where's my CDATA?
// Let's try explicit casts
print_r( (string)$xml );
print_r( (string)$xml2 );
/*
Text1 & XML entities
Text2 & raw data
*/
// Much better
?>
13-Jun-2008 01:10
Entity Oddities.
in reply to m0sh3 at hotmail dot com
This bug was fixed in PHP 5.2.6 (#44478)
However beware that the addChild method require input to be entity encoded. IE. you can NOT input "one & two" you have to input "one & two".
Assigning directly (aside from the bug fixed in 5.2.6) does not have this requirement.
11-Jun-2008 08:40
A small 'n nice function to extract an XML and return it as an array. If there is a bug, please let me know here. I testet it for my purposes and it works.
<?php
public function extractXML($xml) {
if (!($xml->children())) {
return (string) $xml;
}
foreach ($xml->children() as $child) {
$name=$child->getName();
if (count($xml->$name)==1) {
$element[$name] = $this->extractXML($child);
} else {
$element[][$name] = $this->extractXML($child);
}
}
return $element;
}
// you can call it this way
$xml = false;
$xml = @simplexml_load_string($xmlstring);
// 1)
if ($xml) {
$array = extractXML($xml);
} else {
$array = false;
}
// 2)
if ($xml) {
$array[$xml->getName()] = extractXML($xml);
} else {
$array = false;
}
?>
22-May-2008 03:24
Be aware of quirks like this one:
<?php
$a = new SimpleXMLElement('<a><b/></a>');
$a->b = 'test & test';
$a->c = 'test & test';
print_r($a);
/*
OUTPUT:
SimpleXMLElement Object
(
[b] => test & test
[c] => test & test
)
*/
20-May-2008 05:16
You can avoid easily, the unpredictable destruction of $_SESSION when loading a simple_xml object (which occurs under _some_ PHP versions) by serializing and unserializing it:
<?php
$oldsession=serialize($_SESSION);
$myxml=@simplexml_load_file('my.xml');
$_SESSION=unserialize($oldsession);
unset ($oldsession);
?>
This worked for me under PHP 5.2.5 while on 5.1.6 there was no need for such workaround at all.
Same workaround should help if there are other superglobals affected by this bug...
21-Apr-2008 05:28
A looked through a lot of the sample code for reading XML files with CDATA, but they didn't work out that well for me. However, I found that the following piece of code worked perfectly for reading through a file using lots of CDATA.
<?php
$article_string = file_get_contents($path);
$article_string = preg_replace_callback('/<!\[CDATA\[(.*)\]\]>/', 'filter_xml', $article_string);
$article_xml = simplexml_load_string($article_string);
function filter_xml($matches) {
return trim(htmlspecialchars($matches[1]));
}
?>
20-Apr-2008 11:39
I found simpleXML to be useful only in cases where the XML was extremely small, otherwise the server would run out of memory (I suspect there is a memory leak or something?). So while searching for alternative parsers, I decided to try a simpler approach. I don't know how this compares with cpu usage, but I know it works with large XML structures. This is more a manual method, but it works for me since I always know what structure of data I will be receiving.
Essentially I just preg_match() unique nodes to find the values I am looking for, or I preg_match_all to find multiple nodes. This puts the results in an array and I can then process this data as I please.
I was unhappy though, that preg_match_all() stores the data twice (requiring twice the memory), one array for all the full pattern matches, and one array for all the sub pattern matches. You could probably write your own function that overcame this. But for now this works for me, and I hope it saves someone else some time as well.
// SAMPLE XML
<RETS ReplyCode="0" ReplyText="Operation Successful">
<COUNT Records="14" />
<DELIMITER value="09" />
<COLUMNS>PropertyID</COLUMNS>
<DATA>521897</DATA>
<DATA>677208</DATA>
<DATA>686037</DATA>
</RETS>
<?PHP
// SAMPLE FUNCTION
function parse_xml($xml) {
// GET DELIMITER (single instance)
$match_res = preg_match('/<DELIMITER value ?= ?"(.*)" ?\/>/', $xml, $matches);
if(!empty($matches[1])) {
$results["delimiter"] = chr($matches[1]);
} else {
// DEFAULT DELIMITER
$results["delimiter"] = "\t";
}
unset($match_res, $matches);
// GET MULTIPLE DATA NODES (multiple instances)
$results["data_count"] = preg_match_all("/<DATA>(.*)<\/DATA>/", $xml, $matches);
// GET MATCHES OF SUB PATTERN, DISCARD THE REST
$results["data"]=$matches[1];
unset($match_res, $matches);
// UNSET XML TO SAVE MEMORY (should unset outside the function as well)
unset($xml);
// RETURN RESULTS ARRAY
return $results;
}
?>
05-Apr-2008 10:43
I'm using SimpleXML to process data sent back from an API request, and I ran into the CDATA problem and the error you get from html entities, and here is a solution i came up with, don't know if it's the most practical, but it's working.
<?php
$str = <<< XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<menu>
<item>
<title><![CDATA[Café]]></title>
<description><![CDATA[Some description.]]></description>
</item>
</menu>
XML;
$str = preg_replace("/\<\!\[CDATA\[(.*?)\]\]\>/ies", "'[CDATA]'.base64_encode('$1').'[/CDATA]'", $str);
$xml = new SimpleXMLElement($str);
// Return item title's
foreach ($xml->item as $item) {
$tmp = (string) $item->title;
$tmp = preg_replace("/\[CDATA\](.*?)\[\/CDATA\]/ies", "base64_decode('$1')", $tmp);
echo $tmp;
}
?>
26-Mar-2008 10:03
If you don't want that the CDATA values get escaped, just load the XML with LIBXML_NOCDATA as an 3rd argument.
Note: A PHP version >= 5.1.0 is required for this to work.
Example:
<?php simplexml_load_file('xmldatei.xml', null, LIBXML_NOCDATA); ?>
19-Mar-2008 11:44
fix the function i posted before, have a wrong quote placement
function implode2($glue="", $var){
if ($var){
foreach ($var as $value){
$array[]=strval($value);
}
return implode($glue, $array);
}
else return false;
}
19-Mar-2008 11:39
implode() can't be used for simplexml's array but foreach() does the thing, make new implode function with foreach() will resolve this problem. use strval() to reset SimpleXMLElement object to a simple string variable.
function simplexml_implode ($glue="", $var){
if ($var){
foreach ($var as $value){
$array[]=strval($value);
}
} return implode($glue, $array);
else return false;
}
11-Mar-2008 06:47
I believe the "proper" solution to wyzzard's problem is:
var_dump( $Hsp->{"Hsp_query-from"} );
04-Mar-2008 04:33
Consider following php code:
$xml=<<<HSPXML
<Hsp>
<Hsp_query-from>10</Hsp_query-from>
</Hsp>
HSPXML;
$Hsp = new SimpleXMLElement($xml);
//You can't simply access Hsp_query-form element because '-' is PHP's operator
var_dump($Hsp->Hsp_query-from);
var_dump($Hsp["Hsp_query-from"]);
var_dump(array_key_exists("Hsp_query-from" , $Hsp));
//Prints:
/*
int(0)
NULL
bool(true)
*/
//so the key exists! but is inaccessible with[] (and {})
//Way around is to create an additional array:
$hsp_array = array();
foreach($Hsp as $key=>$value)
{
$hsp_array[$key]=$value;
}
var_dump($hsp_array["Hsp_query-from"]);
//the proper result:
object(SimpleXMLElement)#3 (1) {
[0]=>
string(2) "10"
}
produced PHP 5.2.5, Windows, Wamp 2.0
24-Feb-2008 07:22
mtudor: It is not worth including in the documentation, as it works exactly as expected.
Using Vegar's XML example (slightly modified):
<?php
$xml ="<doc>
<multi attr1='one' attr2='two' attr3='three'>One</multi>
<multi>Two</multi>
</doc>";
$xml = new SimpleXMLElement($xml);
echo count($xml->multi); //returns 2
echo count($xml->multi->attributes()); //returns 3
echo count($xml->multi[0]->attributes()); //returns 3
echo count($xml->multi[1]->attributes()); //returns 0
?>
So the only part that may throw people off is the fact that if an array key is not given, it will assume the first element. This should however be obvious since you work from this assumption whenever referring to a single element:
<?php
$xml ="<doc>
<single>
<tag>A Tag</tag>
</single>
</doc>";
$xml = new SimpleXMLElement($xml);
echo $xml->single[0]->tag[0]; //returns 'A Tag'
echo $xml->single->tag; //returns 'A Tag'
?>
22-Feb-2008 10:12
One more comment to Jason's XMLToArray function. The best and right way, which I found for multidimensional arrays and tags with the same names is this hook(see method convert). This will right save all data in right array without losing first element of same_nodes_name (this happens when we find first element, saves it and when find later one more with the same_nodes_name, we need to recreate array to save old data in [0] element of new array, otherwise our first element will be outside of array /see my previous post/). Also I made this little class for facility.
final class XMLToArray {
public function parse($xml) {
return $this->convert(simplexml_load_string($xml));
}
function convert($xml) {
if ($xml instanceof SimpleXMLElement) {
$children = $xml->children();
$return = null;
}
foreach ($children as $element => $value) {
if ($value instanceof SimpleXMLElement) {
$values = (array)$value->children();
if (count($values) > 0) {
if (is_array($return[$element])) {
//hook
foreach ($return[$element] as $k=>$v) {
if (!is_int($k)) {
$return[$element][0][$k] = $v;
unset($return[$element][$k]);
}
}
$return[$element][] = $this->convert($value);
} else {
$return[$element] = $this->convert($value);
}
} else {
if (!isset($return[$element])) {
$return[$element] = (string)$value;
} else {
if (!is_array($return[$element])) {
$return[$element] = array($return[$element], (string)$value);
} else {
$return[$element][] = (string)$value;
}
}
}
}
}
if (is_array($return)) {
return $return;
} else {
return false;
}
}
}
22-Feb-2008 03:30
As for Jason Sheets <jsheets at shadonet dot com> function - very good function, thanks for work! I've made only some changes in recursion to allow same_nodes_name support, like this one:
<node2>
<node3>
<node4>test</node4>
<node4>test1</node4>
</node3>
<node5/>
</node2>
this hack allows you to have in array both "node4".
Simply remove line:
$return[$element] = XMLToArray($value);
and add this one
(!isset($return[$element])) ? $return[$element] = XMLToArray($value) : $return[$element][] = XMLToArray($value);
21-Feb-2008 03:42
SimpleXMLElement is tricky to extend, because -> is overloaded and prevents you to set and use properties. So you can only add methods.
There is a trick to do it however, by extending SimpleXMLElement (or SimpleXMLIterator too), described in this blog post:
http://blog.extend.ws/2008/02/20/extending-simplexml/
18-Feb-2008 06:39
Be careful using nested SimpleXML objects in double quoted strings.
<?php
$xmlstring = '<root><node>123</node><foo><bar>456</bar></foo></root>';
$root = simplexml_load_string($xmlstring);
echo "Node is: $root->node"; // Works: Node is 123
echo "Bar is: $root->foo->bar"; // Doesn't work, outputs: Bar is: ->bar
// use curly brackets to fix
echo "Bar is: {$root->foo->bar}"; // Works: Bar is 456
?>
12-Feb-2008 09:46
TIP: if you run into memory issue using simpleXML.
You may want to use PHP>5.2.5 in order to avoid that: http://bugs.php.net/bug.php?id=38604
If not possible, avoid "foreach ( $xmldata->node as $node )" like calls.
08-Feb-2008 08:47
It doesn't seem to be documented anywhere, but you can refer to an element "value" for the purpose of changing it like so:
<?php
$xml = simplexml_load_string('<root><number>1</number></root>');
echo $xml->asXml(). "\n\n";
$xml->number->{0} = $xml->number->{0} + 1;
echo $xml->asXml();
?>
echos:
<?xml version="1.0"?>
<root><number>1</number></root>
<?xml version="1.0"?>
<root><number>2</number></root>
However, this only works with a direct assignment, not with any of the other operators:
<?php
$xml = simplexml_load_string('<root><number>1</number></root>');
echo $xml->asXml(). "\n\n";
$xml->number->{0} += 1;
// Or:
$xml->number->{0}++;
echo $xml->asXml();
?>
Both of the above cases would result in:
<?xml version="1.0"?>
<root><number>1</number></root>
<?xml version="1.0"?>
<root><number>1<0/></number></root>
04-Feb-2008 04:38
As was said before don't use var_dump() or print_r() to see SimpleXML object structure as they do not returns always what you expect.
Consider the following:
<?php
// data in xml
$xml_txt = '
<root>
<folder ID="65" active="1" permission="1"><![CDATA[aaaa]]></folder>
<folder ID="65" active="1" permission="1"><![CDATA[bbbb]]></folder>
</root>';
// load xml into SimpleXML object
$xml = simplexml_load_string($xml_txt, 'SimpleXMLElement', LIBXML_NOCDATA);//LIBXML_NOCDATA LIBXML_NOWARNING
// see object structure
print_r($xml);
/* this prints
SimpleXMLElement Object
(
[folder] => Array
(
[0] => aaaa
[1] => bbbb
)
)
*/
// but...
foreach ($xml->folder as $value){
print_r($value);
}
/* prints complete structure of each folder element:
SimpleXMLElement Object
(
[@attributes] => Array
(
[ID] => 65
[active] => 1
[permission] => 1
)
[0] => aaaa
)
SimpleXMLElement Object
(
[@attributes] => Array
(
[ID] => 65
[active] => 1
[permission] => 1
)
[0] => bbbb
)
*/
?>
23-Jan-2008 03:50
Be careful when trying to access a repeated xml tag such as in the example below:
<?php
$xmlText = "<test>
<group>
<multi>One</multi>
<multi>Two</multi>
</group>
</test>";
$xml = new SimpleXMLElement($xmlText);
?>
SimpleXML will convert this into an object as follows:
SimpleXMLElement Object
(
[group] => SimpleXMLElement Object
(
[multi] => Array
(
[0] => One
[1] => Two
)
)
)
From this, you may think as I did, that the following snippet will yield an Array with two elements:
<?php
print_r($xml->group->multi)
?>
What actually happens is that SimpleXMLElement assumes that you want the first element in the array, so the above snippet will actually return:
SimpleXMLElement Object
(
[0] => One
)
Accessing using Array notation:
<?php
print_r($xml->group->multi[1]);
?>
Will return:
SimpleXMLElement Object
(
[0] => Two
)
as would be expected. It seems that SimpleXML has a mechanism to determine whether or not you are using Array notation and if not, returning the first element of the array.
Unfortunately, this is a real problem for using functions such as count() and is_array(). is_array($xml->group->multi) will actually return false, as you are actually testing against the first element of the multi array, wrapped in a SimpleXMLElement object.
The solution, it would seem, is one of the following:
1. Access the element using xpath, which returns an array that can be counted and edited etc.:
<?php
print_r($xml->group->xpath("multi"));
?>
2. Use isset to detect if array index 1 is present:
<?php
if(isset($xml->group->multi[1]))
{
echo "The XML has more than one multi element";
}
?>
See http://vega.rd.no/article/simplexml-not-that-simple for further information.
I wonder if it might be worth including this in the body of the documentation, as it is quite a big gotcha?
02-Jan-2008 03:01
<?php
/**
* Remove node/nodes xml with xpath
*
* @param SimpleXMLElement $xml
* @param string XPath $path
* @param string ('one'|'child'|'all') $multi
*
* Use:
*
* Example xml file - http://ru2.php.net/manual/ru/ref.simplexml.php
*
* $xml = simplexml_load_file($xmlfile);
*
* //1. remove only 1 node (without child nodes)
* // $path must return only 1 (unique) node without child nodes
* removeNode($xml, '//movie/rating[@type="thumbs"]');
*
* //2. remove 1 node (with 1 child nodes)
* // $path can return any nodes - will be removed only first node
* // with all child nodes
* removeNode($xml, '//characters', 'child')
*
* //3. remove all nodes (with child nodes)
* // $path can return any nodes - will be removed all
* // with child nodes
* removeNode($xml, '//rating', 'all')
*
* $xml->asXML($xmlfile);
*
*/
function removeNode($xml, $path, $multi='one')
{
$result = $xml->xpath($path);
# for wrong $path
if (!isset($result[0])) return false;
switch ($multi) {
case 'all':
$errlevel = error_reporting(E_ALL & ~E_WARNING);
foreach ($result as $r) unset ($r[0]);
error_reporting($errlevel);
return true;
case 'child':
unset($result[0][0]);
return true;
case 'one':
if (count($result[0]->children())==0 && count($result)==1) {
unset($result[0][0]);
return true;
}
default:
return false;
}
}
?>
p.s. after use (if deleted all nodes):
<?php
function trimXml($xmlfile)
{
$dom = new DOMDocument();
$dom->preserveWhiteSpace = false;
if (!$dom->load($xmlfile)) return false;
$dom->formatOutput = true;
if (!$dom->save($xmlfile)) return false;
return true;
}
?>
02-Jan-2008 12:46
The earlier poster is right in that simplexml becomes pretty useless when you have mixed-content XML. This means that you basically cant use simplexml for (X)HTML, which is a bit of a blow.
Here's a potential solution (hack) for those of you just finding out the mixed-content problem and not wanting to rewrite using DOM. This probably only really works if you have well-formed xhtml, which in my case I have from running some html through Tidy beforehand.
Before passing to simplexml, run a couple of regexs on the html to put all the mixed content inside <span>s. This gets handled as separate elements in the tree.
// put all text content inside a <span> because simplexmlelement
// doesnt handle mixed content.
// lesson learnt: dont use simplexmlelement for html
$html = preg_replace('|>\s*<|','><',$html);
$html = preg_replace('|>\s*([^<]+)\s*|','><span>\1</span>',$html);
19-Nov-2007 06:49
For more info regarding encodings in SimpleXML and DOM, please visit http://www.onphp5.com/article/57
17-Nov-2007 04:37
It does not say in the docs, but SimpleXML will convert all text into UTF-8, if the source XML declaration has another encoding. Eg, if the source has the following XML decl:
<?xml version="1.0" encoding="windows-1251" ?>
all the text in the resulting SimpleXMLElement will be in UTF-8 automatically.
08-Nov-2007 06:14
If you want to compare two XML documents for loose equality -- i.e. they both have the same attributes, children and text content, over all namespaces -- then you can use the function provided below.
I don't know of any easier way to do this -- it would make sense to be part of the SimpleXML extension, I'm sure.
<?php
$xml1 = new SimpleXMLElement(file_get_contents('file1.xml'));
$xml2 = new SimpleXMLElement(file_get_contents('file2.xml'));
$result = xml_is_equal($xml1, $xml2);
if ($result === true) {
// the XML documents are the same
} else {
// they are different: print the reason why
printf(STDERR, "XML documents are different: $result");
}
?>
xml_is_equal() source: http://www.jevon.org/wiki/Comparing_Two_SimpleXML_Documents
30-Oct-2007 08:32
This function could be useful to somebody if you want to insert an XML into another when building an XML from many different files.
Note that you must specify a name for the node in which the child files content/node will be inserted :
foreach (xxx as $firstCondition){
$xml_parent = simplexml_load_file("$firstCondition.xml");
foreach (yyy as $secondCondition){
$xml_children = simplexml_load_file("secondCondition.xml");
SimpleXMLElementObj_into_xml($xml_parent , $xml_children , 'linkingNode'); //will insert every nodes of the files at the end of the parent_xml
} }
function SimpleXMLElementObj_into_xml($xml_parent, $xml_children, $linkingNode= "linkingNode" , $child_count = 0 , $xml = false ){
if(!$xml) {
$xml = $xml_parent->addChild($linkingNode);
}else{
$xml = $xml_parent[$child_count];
}
$child_count = 0;
foreach($xml_children->children() as $k => $v) {
if($xml->$k){
$child_count++;
}
if($v->children()) {
$xml->addChild($k);
SimpleXMLElementObj_into_xml($xml->$k, $v, '', $child_count, true);
}else{
$xml->addChild($k, $v);
}
}
return $xml;
}
Thanks to some contributor whom I've taken the structure of this function.
23-Oct-2007 05:35
If you need to do math calculations on values extracted from simplexml document, you might need to cast the value as float to prevent precision loss. Here is an example:
<?
$objXML = new SimpleXMLElement('<test x="-123.45"></test>');
//Shows correctly
echo $objXML['x']."\n";
//We loose the decimals
echo $objXML['x'] + $objXML['x']."\n";
$x = $objXML['x'];
//This works if we cast the amounts
echo (float)$objXML['x'] + (float)$objXML['x']."\n";
//Calculated on a string, no problem
echo "-123.45" + "-123.45";
?>
This is due to the fact that $objXML['x'] is not a string (php would cast it automatically) neither a float, but a SimpleXMLElement object.
"echo var_dump($x);" will output this
~~
object(SimpleXMLElement)#3 (1) {
[0]=>
string(7) "-123.45"
}
~~
I opened a bug request on php but here is the answer they gave me:
~~
Status: Won't fix
The behavior is defined by the engine not the extension. When performing mathematical operations on objects, they are treated as integers. It is up to the user to cast the object to the appropriate type to maintain proper precision.
~~
28-Aug-2007 06:04
@Leonid Kogan:
Your script is not working in all cases. It has problem with non-uniques elements, ie:
<?php $array = array( 'root' => array( 'first' => array( 'value', 'other' ) ) );?>
In XML it should looks like this:
<root>
<first>value</first>
<first>other</first>
</root>
But it will looks:
<root>
<first>
<1>value</1>
<2>other</2>
</first>
</root>
which is wrong.
27-Aug-2007 05:07
I had a problem with entities.
My first solution:
I saved Data that way:
$ENTRY_->
addchild('Nachricht',htmlentities($_POST["blog"]));
Had Entities in the XML-File like:
<!ENTITY auml "&auml">
And I loaded the Data that way:
html_entity_decode($ENTRY->Nachname);
But after saving and
loading the xml-file the entity-entry
<!ENTITY auml "&auml">
disappeared. strange...
My second solution:
With saving the Data this way:
$ENTRY_->
addchild('Nachricht',htmlentities(htmlentities($_POST["blog"])));
I can now load it with html_entity_decode without the
entity-entry in the XML-file!
I tested it with äöü.
Hope it helpes.
25-Aug-2007 07:29
You dont need a function named "fixCDATA".
Work with "htmlentities" and "html_entity_decode" is
better.
A short example:
$xml->addChild($key, "<![CDATA[".htmlentities($value)."]]>");
print_r((html_entity_decode($files_xml->asXML())));
Greetings S.P. aka darki777
21-Aug-2007 07:40
You can't use CDATA with SimpleXML, but there is a way around it. Wrap your child in CDATA like this:
<? $listing->addChild( 'description', '<![CDATA[' . $row['description'] . ']]>' ); ?>
And then when you display the XML, run it through this function:
<?
function fixCDATA($string) {
$find[] = '<![CDATA[';
$replace[] = '<![CDATA[';
$find[] = ']]>';
$replace[] = ']]>';
return $string = str_replace($find, $replace, $string);
}
$xml = fixCDATA( $xml->asXML() );
echo $xml;
?>
16-Aug-2007 08:28
When creating a new XML document and adding text with umlauts and such
$SimpleXMLElement->asXML();
will silently NOT output any content with umlauts.
Use htmlentities () while adding Umlauts & co to solve the "problem"
16-Aug-2007 02:10
if you want to export an array as xml you can use this script
<?php
$test=array("TEST"=>"nurso",
"none"=>null,
"a"=>"b",
array(
"c"=>"d",
array("d"=>"e")));
$xml=array_to_simplexml($test);
print->($xml);
print_r($xml->asXML());
function array_to_simplexml($array, $name="config" ,&$xml=null )
{
if(is_null($xml))
{
$xml = new SimpleXMLElement("<{$name}/>");
}
foreach($array as $key => $value)
{
if(is_array($value))
{
$xml->addChild($key);
array_to_simplexml($value, $name, $xml->$key);
}
else
{
$xml->addChild($key, $value);
}
}
return $xml;
}
?>
03-Aug-2007 07:40
If you're handling lots of HTML or mixed-content XML you'll probably want to use the DOM functions instead of SimpleXML. Take this for example:
<?php
$html = new SimpleXMLElement('<div><p>Some text, <a href="#">a link,</a> more text</p></div>');
echo $html->p->a,"<br>\n"; // "a link,"
echo $html->p; // "Some text, more text" (!)
?>
In the above example reconstructing the original markup is impossible because of the way SimpleXML represents the data.
03-Aug-2007 05:49
For the poor users who can only use a PHP4 solution, there are some alternatives to it like minixml or my simple php class called sxml which runs with php5 as well as with php4: http://www.shop24-7.info/32-0-simplexml-alternative-php4.html
It is easy to modify and has a clear structure. The result of the class is an array which owns the xml file's data.
have fun with it
24-Jul-2007 07:41
When using Apache & PHP on windows and you try to do a direct typecast to string or integer of your SimpleXMLElement objects, your Apache MAY crash (mine did :-( ).
would look like this:
$id = (int)$xml->id;
so try other things like firts converting it to a string with strval() NOT direct typecast - this won't work neither.
seems like direct typecast doesnt work on objects (doesn' suprise me very much)
19-Jul-2007 03:45
<to bens at effortlessis dot com>
I see your problem, but there's a workaround.
After loading your SimpleXML object into $xml, call $xml->getName () and you'll get your root tag name.
I use PHP 5.1.4 on Apache 1.3.33 on MacOS X and this works on it. I hope there was no SimpleXML ability downgrading :o)
Also, I kinda remember reading somewhere in this doc an advise saying "Don't use data structures parsers like var_dump() on SimpleXML." This may be the reason. And you may will work your second problem around using SimpleXMLElement->attributes() and SimpleXMLElement->getName ().
18-Jul-2007 01:58
Data can be lost when using SimpleXML!!! Consider the following XML:
<root position="base">
<edition id="20070705" onsale="2007-07-06" online="2007-07-06 09:00">
<headline maxchars="50" someattr="whatever">
<cell attr="meaningless">The new issue</cell>
</headline>
</edition>
<monkey id="123">
<face class="round">red</face>
<face class="square">pink</face>
</monkey>
</root>
parsed as follows
$res = simplexml_load_string($xml);
print_r($res);
1) The name of the first (root?) tag of the document is lost - there is no way to find out what the root tag was called. (in this case, I called it "root") However, the attributes within that tag are preserved. (position="base")
2) The innermost tag's attributes are lost. (apparently, any tag with data in it) Note that attr='meaningless' in the cell tag, as well as the class="square"/"round" is lost.
This is when using PHP 5.1.6 on Fedora Core 6 with stock RPMs.
04-Jul-2007 06:23
Correct me if I'm wrong, but I think the output of the SimpleXMLElement Object is dependent on the PHP version. Consider this code:
$xml = '<edition id="20070705" onsale="2007-07-06" online="2007-07-06 09:00">
<headline maxchars="50">The new issue</headline>
</edition>';
$res = simplexml_load_string($xml);
print("<pre>");
print_r($res);
print("</pre>");
PHP version 5.0.4 will return this:
SimpleXMLElement Object
(
[headline] => The new issue
)
While PHP version 5.2.1 returns (some) attributes as well:
SimpleXMLElement Object
(
[@attributes] => Array
(
[id] => 20070705
[onsale] => 2007-07-06
[online] => 2007-07-06 09:00
)
[headline] => The new issue
)
15-Jun-2007 09:27
In response to webmaster at mavoric dot net comment.
It is a complete non-sense to use include() to retrieve an external RSS feed, remember that include is intented to **parse and execute PHP code** and an RSS feed is, quite obivously, **not** PHP code.
people that wants to read external resources should use file_get_contents, fopen or an SplFileObject, never **ever** use include() with remote resources.
05-Jun-2007 08:26
If you are using the above examples to get at external XML/RSS files and are encountering errors, you need to understand that PHP Version 5.2.3 + forbids the use of using include to get at external XML/RSS files . You need to change the start of the code slightly...
<?php
$url = 'http://www.a_external_site.com/example.xml';
$rss_file = file_get_contents($url);
$xml = new SimpleXMLElement($rss_file);
echo $xml->movie[0]->plot; // "So this language. It's like..."
?>
Just replace the first two lines of the exsisting code with the first two of the above code.
30-Apr-2007 02:55
I've implemented a XML Pull Parser for PHP 5 that somewhat mimics SimpleXML - but doesn't need to load the whole document in memory :
http://tinyurl.com/2e9ew5
08-Apr-2007 05:14
It is a grave misfortune that this doesn't work:
<?
$node = new SimpleXml;
echo $node->{"namespace:tagname"}[attribute'];
?>
Instead you have to go through loops!
<?
$namespace = $node->children( 'namespace' );
$attributes = $namespa