XML Parser

<?php
/*
 * XmlParser.class.php -- Version 14-Jan-2006
 *
 * Copyright (c) 2004-2006 Jochen Kupperschmidt
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * The above license is the MIT License. It was copied from the website of the
 * Open Source Initiative: http://www.opensource.org/licenses/mit-license.php
 *   _                               _
 *  | |_ ___ _____ ___ _ _ _ ___ ___| |_
 *  |   | . |     | ._| | | | . |  _| . /
 *  |_|_|___|_|_|_|___|_____|___|_| |_|_\
 *    http://homework.nwsnet.de/
 *
 * This can be seen as XML SAX parser framework. With it, you can easily create
 * parsers for your custom XML tags.
 *
 * To create a parser, write a subclass of the XmlParser class and add methods
 * which names start with 'handle_', followed by the tag name it should react
 * to. When the parser reaches a closing tag, the corresponding method will be
 * called. Its parameters will be an associative array containing the
 * attributes firstly and a string containing the character data (the data
 * enclosed by start and end tag of an element) secondly.
 *
 * Example usage:
 *
 *      # Include the parser framework.
 *      require_once('XmlParser.class.php');
 *
 *      # Subclass it.
 *      class XmlLinkParser extends XmlParser {
 *
 *          # This would react on
 *          #   <link href="http://www.example.com/">Example.com</link>
 *          # and return
 *          #   <a href="http://www.example.com/"
 *          #      target="_blank"
 *          #      title="Visit Example.com"
 *          #      >Example.com (http://www.example.com/)</a>
 *          function handle_link($attr, $cdata) {
 *              return sprintf(
 *                  '<a href="%s" target="_blank" title="Visit %s">%s (%s)</a>',
 *                  $attr['href'], $cdata, $cdata, $attr['href']);
 *          }
 *      }
 *
 *      # Instanciate the custom parser.
 *      $parser = new XmlLinkParser();
 *
 *      # Parse a file with XML content and display the result.
 *      echo $parser->parseFile('example.xml');
 *
 *      # Parse a string and display the result.
 *      echo $parser->parseString('<div>
 *      <h1>Some links</h1>
 *      <ul>
 *          <li><link href="http://www.example.com/one/">Example #1</link></li>
 *          <li><link href="http://www.example.com/two/">Example #2</link></li>
 *          <li><link href="http://www.example.com/three/">Example #3</link></li>
 *      </ul>
 *      </div>
 *      ');
 */

# A stack.
#
# Elements can be pushed upon or popped off the top.
class Stack {

    var $stack = array();

    # Place an element on the stack.
    function push($data) {
        array_push($this->stack, $data);
    }

    # Take an element off the stack.
    function pop() {
        if (! $this->stack) {
            trigger_error('Stack buffer underrun.', E_USER_ERROR);
        }
        return array_pop($this->stack);
    }
}

# The basic SAX parser.
#
# Subclasses may implement methods which names begin with "handle_", followed
# by the tag name to be reacted to.
#
# Example:
#     function handle_something($attr, $cdata) { ... }
# It will be invoked when the closing part of the <something> element is found.
class XmlParser {

    var $stack;

    # Read XML from file.
    function parseFile($filename) {
        if (! file_exists($filename)) {
            trigger_error('File not found.', E_USER_WARNING);
            return False;
        }
        return $this->parseString(implode('', file($filename)));
    }

    # Read XML from string.
    function parseString($data) {
        $this->stack = new Stack();
        $this->stack->push(array('content' => ''));

        $parser = xml_parser_create('iso-8859-1');
        xml_set_object($parser, &$this);
        xml_set_element_handler($parser, 'startElement', 'endElement');
        xml_set_character_data_handler($parser, 'characters');
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
        if (! xml_parse($parser, $data)) {
            $errorCode = xml_get_error_code($parser);
            $errorString = xml_error_string($errorCode);
            $error = sprintf('Error parsing XML data (#%d: %s).', $errorCode,
                $errorString);
            trigger_error($error, E_USER_WARNING);
        }
        xml_parser_free($parser);

        $first = $this->stack->pop();
        return $first['content'];
    }

    # ---------------------------------------------------------------- #

    # Called when an element starts.
    function startElement($parser, $tag, $attributes) {
        $this->stack->push(array(
            'attributes' => $attributes,
            'content' => ''
            ));
    }

    # Called when character data is found.
    function characters($parser, $cdata) {
        $data = $this->stack->pop();
        $data['content'] .= $cdata;
        $this->stack->push($data);
    }

    # Called when an element ends.
    function endElement($parser, $tag) {
        $elementHandler = 'handle_' . strtolower($tag);
        $data = $this->stack->pop();
        if (method_exists($this, $elementHandler)) {
            $buffer = call_user_func(array($this, $elementHandler),
                $data['attributes'], $data['content']);
        } else {
            $buffer = '<' . $tag;
            foreach ($data['attributes'] as $attr => $value) {
                $buffer .= sprintf(' %s="%s"', $attr, $value);
            }
            if (0 == strlen($data['content'])) {
                $buffer .= '/>';
            } else {
                $buffer .= sprintf('>%s</%s>', $data['content'], $tag);
            }
        }
        $sublevel = $this->stack->pop();
        $sublevel['content'] .= $buffer;
        $this->stack->push($sublevel);
    }
}
?>