I am using XMLReader and avoided SimpleXML because I must handle a huge file and for memory issues, SimpleXML is not the ideal solution. However, even I coded the below script in SimpleXML, it gives me the result really much faster.
Because speed and memory is a must for this project, is there any way to speed up this code? What it does, it filters the books and show those that contains the word jQuery.
error_reporting(~0);
$xml = <<<EOD
<?xml version="1.0" encoding="ISO-8859-1"?>
<library>
<book isbn="781">
<name>SCJP 1.5</name>
<info>Sun Certified Java Programmer book</info>
</book>
<book isbn="194">
<name>jQuery is Awesome!</name>
<info>jQuery Reference Book</info>
</book>
<book isbn="199">
<name>jQuery 101</name>
<info>All you need to know about jQuery</info>
</book>
</library>
EOD;
$file = 'data://text/plain;base64,'.base64_encode($xml);
class XMLBookIterator implements iterator
{
private $file;
private $reader;
private $state;
private $key = 0;
private $book;
private $valid;
public function __construct($file)
{
$this->file = $file;
}
public function current()
{
return $this->book;
}
public function key()
{
return $this->key;
}
public function next()
{
$reader = $this->reader;
while ($next = $reader->read())
{
switch ($reader->nodeType)
{
case (XMLREADER::ELEMENT):
$case = $reader->localName.'|'.$this->state;
switch($case)
{
case 'library|0':
break;
case 'book|1':
$this->book = new stdClass;
$this->book->isbn = $reader->getAttribute('isbn');
break;
case 'name|2':
$this->book->name = $reader->readInnerXML();
break;
case 'info|3':
$this->book->info = $reader->readInnerXML();
$this->state=0;
break;
default:
throw new Exception(sprintf('Invalid State: %s.', $case));
}
$this->state++;
if ($this->state === 1) break 2;
}
}
$this->valid = $next;
$this->key++;
}
public function rewind()
{
if ($this->reader) $this->reader->close();
$this->reader = new XMLReader();
$this->reader->open($this->file);
$this->state = 0;
$this->next(); // move to first element or invalidate
if ($this->valid) $this->next(); // get first book
$this->key = 0;
}
public function valid()
{
return $this->valid;
}
}
class BookFilterIterator extends FilterIterator
{
private $term;
public function __construct($iterator, $term)
{
parent::__construct($iterator);
$this->term = $term;
}
public function accept() {
$book = parent::current();
$name = $book->name;
return false !== stripos($name, $this->term);
}
}
// filtered books:
$books = new XMLBookIterator($file);
$filtered = new BookFilterIterator($books, 'jQuery');
foreach($filtered as $key => $book)
{
echo 'book (', $key, '):', "\n";
print_r($book);
}
1 Answer 1
Your code seems quite tidy and easy to understand, congratulations. But maybe you can check for built in functionality. XPath can search items for you:
http://www.php.net/manual/en/simplexmlelement.xpath.php
http://www.w3schools.com/xpath/xpath_syntax.asp