Sometimes you have an unusual URL that doesn't actually point to an xml file but still returns xml as output (Like the Battlefield Heroes generated syndication urls). Using get_file_contents(url) you can retrieve the xml data from these urls and pass it as a variable for processing as an XML String.
Unfortunately simpleXML or xml DOM cannot process all xml strings. Some have error boxes added to the end of them (such as Battlefield Heroes syndicated news). These boxes cause an end of file sort of error and closes out the script. XMLReader grabs data from these strings without error.
The XMLReader class
Einführung
Die XMLReader-Erweiterung ist ein fortschreitender XML-Parser. Der Reader agiert wie ein Zeiger, der einen Datenstrom durchläuft und jeden vorbeikommenden Knoten der Reihe nach verarbeitet.
Klassenbeschreibung
$name
)Eigenschaften
- attributeCount
-
Anzahl der Attribute des aktuellen Knotens
- baseURI
-
Die Basis URI des Knotens
- depth
-
Tiefe des Knotens im Baum, beginnend mit 0
- hasAttributes
-
Ob der Knoten Attribute besitzt
- hasValue
-
Ob der Knoten einen Text-Wert besitzt
- isDefault
-
Ob das Attribute als Standard aus der DTD übernommen wurde
- isEmptyElement
-
Ob das Element leer ist
- localName
-
Der lokale Name des Knotens
- name
-
Der qualifizierte Name des Knotens
- namespaceURI
-
Die URI des Namespaces, welcher mit dem Knoten assoziiert ist
- nodeType
-
Der Typ des Knotens
- prefix
-
Der Präfix, welcher mit dem Namespaces des Knotens assoziiert wird
- value
-
Der Text-Wert des Knotens
- xmlLang
-
Der xml:lang Scope des Knotens
Vordefinierte Konstanten
XMLReader Node Typen
-
XMLReader::NONE -
Kein Typ
-
XMLReader::ELEMENT -
Startendes Element
-
XMLReader::ATTRIBUTE -
Attribut
-
XMLReader::TEXT -
Text
-
XMLReader::CDATA -
CDATA Abschnitt
-
XMLReader::ENTITY_REF -
Entität-Referenz
-
XMLReader::ENTITY -
Entität-Deklaration
-
XMLReader::PI -
Verarbeitungsanweisung
-
XMLReader::COMMENT -
Kommentar
-
XMLReader::DOC -
Dokument Knoten
-
XMLReader::DOC_TYPE -
Dokument Typ
-
XMLReader::DOC_FRAGMENT -
Dokument Fragment Knoten
-
XMLReader::NOTATION -
Notation
-
XMLReader::WHITESPACE -
Leerzeichen
-
XMLReader::SIGNIFICANT_WHITESPACE -
Signifikante Leerzeichen
-
XMLReader::END_ELEMENT -
Schließendes Element
-
XMLReader::END_ENTITY -
Entitätsende
-
XMLReader::XML_DECLARATION -
XML Deklaration
XMLReader Parser Optionen
-
XMLReader::LOADDTD -
DTD laden aber nicht validieren
-
XMLReader::DEFAULTATTRS -
DTD und Standardattribute laden aber nicht validieren
-
XMLReader::VALIDATE -
DTD laden und beim Parsen validieren
-
XMLReader::SUBST_ENTITIES -
Entitäten ersetzen und Referenzen erweitern
Inhaltsverzeichnis
- XMLReader::close — XMLReader-Eingabe beenden
- XMLReader::expand — Gibt eine Kopie des aktuellen Knotens als DOM-Objekt zurück
- XMLReade::getAttribute — Gibt den Wert eines Attributes nach Namen zurück
- XMLReader::getAttributeNo — Gibt den Wert eines Attributes nach Position zurück
- XMLReader::getAttributeNs — Gibt den Wert eines Attributes nach lokalem Namen und URI zurück
- XMLReader::getParserProperty — Zeigt an, ob die angegebene Eigenschaft gesetzt wurde
- XMLReader::isValid — Zeigt an, ob das geparste Dokument valide ist
- XMLReader::lookupNamespace — Prüfen, ob ein Namensbereich für einen Präfix vorhanden ist
- XMLReader::moveToAttribute — Zeiger auf benanntes Attribut setzen
- XMLReader::moveToAttributeNo — Move cursor to an attribute by index
- XMLReader::moveToAttributeNs — Move cursor to a named attribute
- XMLReader->moveToElement — Zeiger positionieren auf das Elternelement des aktuellen Attributes
- XMLReader::moveToFirstAttribute — Zeiger auf erstes Attribut setzen
- XMLReader::moveToNextAttribute — Zeiger auf nächstes Attribut setzen
- XMLReader::next — Zeiger auf nächstes Element setzen und Kinder überspringen
- XMLReader::open — URI angeben, die auf XML-Inhalt verweist, der geparst werden soll
- XMLReader::read — Zeiger auf das nächste Element setzen
- XMLReader::readInnerXML — Erhalte XML des aktuellen Knotens
- XMLReader::readOuterXML — Erhalte XML des aktuellen Knotens, inklusive den Knoten selbst
- XMLReader::readString — Liest den Inhalt des aktuellen Knotens als Zeichenkette
- XMLReader::setParserProperty — Parser-Optionen setzen
- XMLReader::setRelaxNGSchema — Dateiname oder URI des RelaxNG-Schema setzen
- XMLReader::setRelaxNGSchemaSource — Zeichenkette setzen, die RelaxNG-Schemadaten enthält
- XMLReader::setSchema — Validiert Dokument gegen XSD
- XMLReader::XML — Zeichenkette setzen, deren Inhalt geparst werden soll
Take care about how to use XMLReader::$isElementEmpty. I don't know if it is a bug or not, but $isElementEmpty is set for the current context and NOT just for the element. If you move your cursor to an attribute, $isElementEmpty will ALWAYS be false.
<?php
$xml = new XMLReader();
$xml->XML('<tag attr="value" />');
$xml->read();
var_dump($xml->isEmptyElement);
$xml->moveToNextAttribute();
var_dump($xml->isEmptyElement);
?>
will output
(bool) true
(bool) false
So be sure to store $isEmptyElement before moving the cursor.
Guys, I hope this example will help
you can erase prints showing the process-
and it will be a piece of nice code.
<?php
function xml2assoc($xml, $name)
{
print "<ul>";
$tree = null;
print("I'm inside " . $name . "<br>");
while($xml->read())
{
if($xml->nodeType == XMLReader::END_ELEMENT)
{
print "</ul>";
return $tree;
}
else if($xml->nodeType == XMLReader::ELEMENT)
{
$node = array();
print("Adding " . $xml->name ."<br>");
$node['tag'] = $xml->name;
if($xml->hasAttributes)
{
$attributes = array();
while($xml->moveToNextAttribute())
{
print("Adding attr " . $xml->name ." = " . $xml->value . "<br>");
$attributes[$xml->name] = $xml->value;
}
$node['attr'] = $attributes;
}
if(!$xml->isEmptyElement)
{
$childs = xml2assoc($xml, $node['tag']);
$node['childs'] = $childs;
}
print($node['tag'] . " added <br>");
$tree[] = $node;
}
else if($xml->nodeType == XMLReader::TEXT)
{
$node = array();
$node['text'] = $xml->value;
$tree[] = $node;
print "text added = " . $node['text'] . "<br>";
}
}
print "returning " . count($tree) . " childs<br>";
print "</ul>";
return $tree;
}
echo "<PRE>";
$xml = new XMLReader();
$xml->open('test.xml');
$assoc = xml2assoc($xml, "root");
$xml->close();
print_r($assoc);
echo "</PRE>";
?>
It reads this xml:
<test>
<hallo volume="loud"> me <br/> lala </hallo>
<hallo> me </hallo>
</test>
Just in case someone is confused, if you're wanting to simply pass a string of XML instead of an entire file, you would do this.
<?php
$foo = new XMLReader();
$foo->xml($STRING);
?>
.... where $STRING holds your XML. You cannot pass it like $foo = $STRING or $foo->xml = $STRING.
The "XML2Assoc" functions noted here should be used with caution... basically they are duplicating the functionality already present in SimpleXML. They may work but they won't scale.
Their are two main uses cases for parsing XML, each suited to either XMLReader or SimpleXML.
1. SimpleXML is an excellent tool for easy access to an XML document tree using native PHP data types. It starts to flounder with massive (> 50M or so) XML documents, as it reads the entire document into memory before it can be processed. SimpleXML will just laugh at you then die when your server runs out of memory (or it will cause a load spike).
2. Aside from the reasoning behind massive XML documents, if you have to deal with massive XML documents, use XMLReader to process them. Don't try and gather an entire XML document into a PHP data structure using XMLReader and a PHP xml2assoc() function, you are reinventing the SimpleXML wheel.
When parsing massive XML documents using XMLReader, gather the data you need to perform an operation then perform it before skipping to the next node. Do not build massive data structures from a massive XML document, your server (and it's admins) will not like you.
A basic parser
<?php
function xml2assoc($xml) {
$arr = array();
if (!preg_match_all('|\<\s*?(\w+).*?\>(.*)\<\/\s*\\1.*?\>|s', $xml, $m)) return $xml;
if (is_array($m[1]))
for ($i = 0;$i < sizeof($m[1]); $i++) $arr[$m[1][$i]] = xml2assoc($m[2][$i]);
else $arr[$m[1]] = xml2assoc($m[2]);
return $arr;
}
?>
XML to ASSOCIATIVE ARRAY
Improved algorithm based on Sergey Aikinkulov's. The problem was that it would overwrite nodes if they had the same tag name. Because of that <a><b/><b/><a> would be read as if <a><b/><a/>. This algorithm handles it better and outputs an easy to understand array:
<?php
function xml2assoc($xml) {
$tree = null;
while($xml->read())
switch ($xml->nodeType) {
case XMLReader::END_ELEMENT: return $tree;
case XMLReader::ELEMENT:
$node = array('tag' => $xml->name, 'value' => $xml->isEmptyElement ? '' : xml2assoc($xml));
if($xml->hasAttributes)
while($xml->moveToNextAttribute())
$node['attributes'][$xml->name] = $xml->value;
$tree[] = $node;
break;
case XMLReader::TEXT:
case XMLReader::CDATA:
$tree .= $xml->value;
}
return $tree;
}
?>
Usage:
myxml.xml:
------
<PERSON>
<NAME>John</NAME>
<PHONE type="home">555-555-555</PHONE>
</PERSON>
----
<?
$xml = new XMLReader();
$xml->open('myxml.xml');
$assoc = xml2assoc($xml);
$xml->close();
print_r($assoc);
?>
Outputs:
Array
(
[0] => Array
(
[tag] => PERSON
[value] => Array
(
[0] => Array
(
[tag] => NAME
[value] => John
)
[1] => Array
(
[tag] => PHONE
[value] => 555-555-555
[attributes] => Array
(
[type] => home
)
)
)
)
)
For reasons that have to do with recursion, it returns an array with the ROOT xml node as the first childNode, rather than to return only the ROOT node.
<?php
//Pull certain elements
$reader = new XMLReader();
$reader->open($xmlfile);
while ($reader->read()) {
switch ($reader->nodeType) {
case (XMLREADER::ELEMENT):
if ($reader->name == "Code")
{
$reader->read();
$code = trim($reader->value);
echo "$code\n";
break;
}
if ($reader->name == "Name")
{
$reader->read();
$customername = trim( $reader->value );
echo "$name\n";
break;
}
if ($reader->name == "Camp")
{
$camp = trim($reader->getAttribute("ID"));
echo "$camp\n";
break;
}
}
}
?>
Thanks rein_baarsma33 AT hotmail DOT com for bugfixes.
This is my new child of XML parsing method based on my and yours modification.
XML2ASSOC Is a complete solution for parsing ordinary XML
<?php
/**
* XML2Assoc Class to creating
* PHP Assoc Array from XML File
*
* @author godseth (AT) o2.pl & rein_baarsma33 (AT) hotmail.com (Bugfixes in parseXml Method)
* @uses XMLReader
*
*/
class Xml2Assoc {
/**
* Optimization Enabled / Disabled
*
* @var bool
*/
protected $bOptimize = false;
/**
* Method for loading XML Data from String
*
* @param string $sXml
* @param bool $bOptimize
*/
public function parseString( $sXml , $bOptimize = false) {
$oXml = new XMLReader();
$this -> bOptimize = (bool) $bOptimize;
try {
// Set String Containing XML data
$oXml->XML($sXml);
// Parse Xml and return result
return $this->parseXml($oXml);
} catch (Exception $e) {
echo $e->getMessage();
}
}
/**
* Method for loading Xml Data from file
*
* @param string $sXmlFilePath
* @param bool $bOptimize
*/
public function parseFile( $sXmlFilePath , $bOptimize = false ) {
$oXml = new XMLReader();
$this -> bOptimize = (bool) $bOptimize;
try {
// Open XML file
$oXml->open($sXmlFilePath);
// // Parse Xml and return result
return $this->parseXml($oXml);
} catch (Exception $e) {
echo $e->getMessage(). ' | Try open file: '.$sXmlFilePath;
}
}
/**
* XML Parser
*
* @param XMLReader $oXml
* @return array
*/
protected function parseXml( XMLReader $oXml ) {
$aAssocXML = null;
$iDc = -1;
while($oXml->read()){
switch ($oXml->nodeType) {
case XMLReader::END_ELEMENT:
if ($this->bOptimize) {
$this->optXml($aAssocXML);
}
return $aAssocXML;
case XMLReader::ELEMENT:
if(!isset($aAssocXML[$oXml->name])) {
if($oXml->hasAttributes) {
$aAssocXML[$oXml->name][] = $oXml->isEmptyElement ? '' : $this->parseXML($oXml);
} else {
if($oXml->isEmptyElement) {
$aAssocXML[$oXml->name] = '';
} else {
$aAssocXML[$oXml->name] = $this->parseXML($oXml);
}
}
} elseif (is_array($aAssocXML[$oXml->name])) {
if (!isset($aAssocXML[$oXml->name][0]))
{
$temp = $aAssocXML[$oXml->name];
foreach ($temp as $sKey=>$sValue)
unset($aAssocXML[$oXml->name][$sKey]);
$aAssocXML[$oXml->name][] = $temp;
}
if($oXml->hasAttributes) {
$aAssocXML[$oXml->name][] = $oXml->isEmptyElement ? '' : $this->parseXML($oXml);
} else {
if($oXml->isEmptyElement) {
$aAssocXML[$oXml->name][] = '';
} else {
$aAssocXML[$oXml->name][] = $this->parseXML($oXml);
}
}
} else {
$mOldVar = $aAssocXML[$oXml->name];
$aAssocXML[$oXml->name] = array($mOldVar);
if($oXml->hasAttributes) {
$aAssocXML[$oXml->name][] = $oXml->isEmptyElement ? '' : $this->parseXML($oXml);
} else {
if($oXml->isEmptyElement) {
$aAssocXML[$oXml->name][] = '';
} else {
$aAssocXML[$oXml->name][] = $this->parseXML($oXml);
}
}
}
if($oXml->hasAttributes) {
$mElement =& $aAssocXML[$oXml->name][count($aAssocXML[$oXml->name]) - 1];
while($oXml->moveToNextAttribute()) {
$mElement[$oXml->name] = $oXml->value;
}
}
break;
case XMLReader::TEXT:
case XMLReader::CDATA:
$aAssocXML[++$iDc] = $oXml->value;
}
}
return $aAssocXML;
}
/**
* Method to optimize assoc tree.
* ( Deleting 0 index when element
* have one attribute / value )
*
* @param array $mData
*/
public function optXml(&$mData) {
if (is_array($mData)) {
if (isset($mData[0]) && count($mData) == 1 ) {
$mData = $mData[0];
if (is_array($mData)) {
foreach ($mData as &$aSub) {
$this->optXml($aSub);
}
}
} else {
foreach ($mData as &$aSub) {
$this->optXml($aSub);
}
}
}
}
}
?>
[EDIT BY danbrown AT php DOT net: Fixes were also provided by "Alex" and (qdog AT qview DOT org) in user notes on this page (since removed).]
Next version xml2assoc with some improve fixes:
- no doubled data
- no buffer arrays
<?php
/*
Read XML structure to associative array
--
Using:
$xml = new XMLReader();
$xml->open([XML file]);
$assoc = xml2assoc($xml);
$xml->close();
*/
function xml2assoc($xml) {
$assoc = null;
while($xml->read()){
switch ($xml->nodeType) {
case XMLReader::END_ELEMENT: return $assoc;
case XMLReader::ELEMENT:
$assoc[$xml->name][] = array('value' => $xml->isEmptyElement ? '' : xml2assoc($xml));
if($xml->hasAttributes){
$el =& $assoc[$xml->name][count($assoc[$xml->name]) - 1];
while($xml->moveToNextAttribute()) $el['attributes'][$xml->name] = $xml->value;
}
break;
case XMLReader::TEXT:
case XMLReader::CDATA: $assoc .= $xml->value;
}
}
return $assoc;
}
?>
make some modify from Sergey Aikinkulov's note
<?php
function xml2assoc(&$xml){
$assoc = NULL;
$n = 0;
while($xml->read()){
if($xml->nodeType == XMLReader::END_ELEMENT) break;
if($xml->nodeType == XMLReader::ELEMENT and !$xml->isEmptyElement){
$assoc[$n]['name'] = $xml->name;
if($xml->hasAttributes) while($xml->moveToNextAttribute()) $assoc[$n]['atr'][$xml->name] = $xml->value;
$assoc[$n]['val'] = xml2assoc($xml);
$n++;
}
else if($xml->isEmptyElement){
$assoc[$n]['name'] = $xml->name;
if($xml->hasAttributes) while($xml->moveToNextAttribute()) $assoc[$n]['atr'][$xml->name] = $xml->value;
$assoc[$n]['val'] = "";
$n++;
}
else if($xml->nodeType == XMLReader::TEXT) $assoc = $xml->value;
}
return $assoc;
}
?>
add else if($xml->isEmptyElement)
may be some xml has emptyelement
<?php
function parseXML($node,$seq,$path) {
global $oldpath;
if (!$node->read())
return;
if ($node->nodeType != 15) {
print '<br/>'.$node->depth;
print '-'.$seq++;
print ' '.$path.'/'.($node->nodeType==3?'text() = ':$node->name);
print $node->value;
if ($node->hasAttributes) {
print ' [hasAttributes: ';
while ($node->moveToNextAttribute()) print '@'.$node->name.' = '.$node->value.' ';
print ']';
}
if ($node->nodeType == 1) {
$oldpath=$path;
$path.='/'.$node->name;
}
parseXML($node,$seq,$path);
}
else parseXML($node,$seq,$oldpath);
}
$source = "<tag1>this<tag2 id='4' name='foo'>is</tag2>a<tag2 id='5'>common</tag2>record</tag1>";
$xml = new XMLReader();
$xml->XML($source);
print htmlspecialchars($source).'<br/>';
parseXML($xml,0,'');
?>
Output:
<tag1>this<tag2 id='4' name='foo'>is</tag2>a<tag2 id='5'>common</tag2>record</tag1>
0-0 /tag1
1-1 /tag1/text() = this
1-2 /tag1/tag2 [hasAttributes: @id = 4 @name = foo ]
2-3 /tag1/text() = is
1-4 /text() = a
1-5 /tag2 [hasAttributes: @id = 5 ]
2-6 /text() = common
1-7 /text() = record
Some more documentation (i.e. examples) would be nice :-)
This is how I read some mysql parameters in an xml file:
<?php
$xml = new XMLReader();
$xml->open("config.xml");
$xml->setParserProperty(2,true); // This seems a little unclear to me - but it worked :)
while ($xml->read()) {
switch ($xml->name) {
case "mysql_host":
$xml->read();
$conf["mysql_host"] = $xml->value;
$xml->read();
break;
case "mysql_username":
$xml->read();
$conf["mysql_user"] = $xml->value;
$xml->read();
break;
case "mysql_password":
$xml->read();
$conf["mysql_pass"] = $xml->value;
$xml->read();
break;
case "mysql_database":
$xml->read();
$conf["mysql_db"] = $xml->value;
$xml->read();
break;
}
}
$xml->close();
?>
The XML file used:
<?xml version='1.0'?>
<MySQL_INIT>
<mysql_host>localhost</mysql_host>
<mysql_database>db_database</mysql_database>
<mysql_username>root</mysql_username>
<mysql_password>password</mysql_password>
</MySQL_INIT>
