<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license,      |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@fast.no>                                    |
// +----------------------------------------------------------------------+
//
// $Id: Parser.php,v 1.22 2002/02/28 08:27:18 sebastian Exp $

require_once "PEAR.php";

/**
* XML Parser class.  This is an XML parser based on PHP's "xml" extension,
* based on the bundled expat library.
*
* @author  Stig Bakken <ssb@fast.no>
* @todo    Tests that need to be made:
*          - error class
*          - mixing character encodings
*          - a test using all expat handlers
*          - options (folding, output charset)
*          - different parsing modes
*
* @notes   - It requires PHP 4.0.4pl1 or greater
*          - From revision 1.17, the function names used by the 'func' mode
*            are in the format "xmltag_$elem", for example: use "xmltag_name"
*            to handle the <name></name> tags of your xml file.
*/
class XML_Parser extends PEAR {
    // {{{ properties

    /**
     * @var  resource  XML parser handle
     */
    var $parser;

    /**
     * @var  resource  File handle if parsing from a file
     */
    var $fp;

    /**
     * @var  boolean  Whether to do case folding
     */
    var $folding = true;

    /**
     * @var  string  Mode of operation, one of "event" or "func"
     */
    var $mode;

    /**
     * Mapping from expat handler function to class method.
     *
     * @var  array
     */
    var $handler = array(
        "character_data_handler"            => "cdataHandler",
        "default_handler"                   => "defaultHandler",
        "processing_instruction_handler"    => "piHandler",
        "unparsed_entitry_decl_handler"     => "unparsedHandler",
        "notation_decl_handler"             => "notationHandler",
        "external_entity_ref_handler"       => "entityrefHandler"
    );

    /** @var string source encoding */
    var $srcenc;

    /** @var string target encoding */
    var $tgtenc;

    /*
    * Use call_user_func when php >= 4.0.7
    * @var boolean
    * @see setMode()
    */
    var $use_call_user_func = true;
    // }}}
    // {{{ constructor

    /**
     * Creates an XML parser.
     *
     * @param    string  source charset encoding, use NULL (default) to use
     *                   whatever the document specifies
     * @param    string  how this parser object should work, "event" for
     *                   startelement/endelement-type events, "func"
     *                   to have it call functions named after elements
     *
     * @see xml_parser_create
     */
    function XML_Parser($srcenc = null, $mode = "event", $tgtenc = null)
    {
        $this->PEAR('XML_Parser_Error');

        if ($srcenc === null) {
            $xp = @xml_parser_create();
        } else {
            $xp = @xml_parser_create($srcenc);
        }
        if (is_resource($xp)) {
            if ($tgtenc !== null) {
                if (!@xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING,
                                            $tgtenc)) {
                    return $this->raiseError("invalid target encoding");
                }
            }
            $this->parser = $xp;
            $this->setMode($mode);
            xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, $this->folding);
        }
        $this->srcenc = $srcenc;
        $this->tgtenc = $tgtenc;
    }
    // }}}

    // {{{ setMode()

    /**
    * Sets the mode and all handler.
    *
    * @param    string
    * @see      $handler
    */
    function setMode($mode) {

        $this->mode = $mode;

        xml_set_object($this->parser, $this);

        switch ($mode) {

            case "func":
                // use call_user_func() when php >= 4.0.7
                // or call_user_method() if not
                if (version_compare(phpversion(), '4.0.7', 'lt')) {
                    $this->use_call_user_func = false;
                } else {
                    $this->use_call_user_func = true;
                }

                xml_set_element_handler($this->parser, "funcStartHandler", "funcEndHandler");
                break;

            case "event":
                xml_set_element_handler($this->parser, "startHandler", "endHandler");
                break;
        }

        foreach ($this->handler as $xml_func => $method)
            if (method_exists($this, $method)) {
                $xml_func = "xml_set_" . $xml_func;
                $xml_func($this->parser, $method);
            }

    }

    // }}}
    // {{{ setInputFile()

    /**
    * Defines
    *
    * @param    string      Filename (full path)
    * @return   resource    fopen handle of the given file
    * @throws   XML_Parser_Error
    * @see      setInput(), parse()
    * @access   public
    */
    function setInputFile($file) {

        $fp = @fopen($file, "rb");
        if (is_resource($fp)) {
            $this->fp = $fp;
            return $fp;
        }

        return $this->raiseError($php_errormsg);
    }

    // }}}
    // {{{ setInput()

    /**
    * Sets the file handle to use with parse().
    *
    * @param    resource    fopen
    * @access   public
    * @see      parse(), setInputFile()
    */
    function setInput($fp) {

        if (is_resource($fp)) {
            $this->fp = $fp;
            return true;
        }

        return $this->raiseError("not a file resource");
    }

    // }}}
    // {{{ parse()

    /**
    * Central parsing function.
    *
    * @throws   XML_Parser_Error
    * @return   boolean true on success
    * @see      parseString()
    * @access   public
    */
    function parse() {

        if (!is_resource($this->fp)) {
            return $this->raiseError("no input");
        }

        while ($data = fread($this->fp, 2048)) {

            $err = $this->parseString($data, feof($this->fp));
            if (PEAR::isError($err)) {
                fclose($this->fp);
                return $err;
            }

        }

        fclose($this->fp);

        return true;
    }

    // }}}
    // {{{ parseString()

    /**
    * Parses a string.
    *
    * @param    string  XML data
    * @param    boolean ???
    * @throws   XML_Parser_Error
    * @return   mixed   true on success or a string with the xml parser error
    */
    function parseString($data, $eof = false) {

        if (!xml_parse($this->parser, $data, $eof)) {
            $err = $this->raiseError($this->parser);
            xml_parser_free($this->parser);
            return $err;
        }

        return true;
    }

    // }}}
    // {{{ funcStartHandler()

    function funcStartHandler($xp, $elem, $attribs)
    {
        $func = 'xmltag_' . $elem;
        if (method_exists($this, $func)) {
            if ($this->use_call_user_func) {
                call_user_func(array(&$this, $func), $xp, $elem, $attribs);
            } else {
                call_user_method($func, $this, $xp, $elem, $attribs);
            }
        }

    }

    // }}}
    // {{{ funcEndHandler()

    function funcEndHandler($xp, $elem)
    {
        $func = 'xmltag_' . $elem . '_';
        if (method_exists($this, $func)) {
            if ($this->use_call_user_func) {
                call_user_func(array(&$this, $func), $xp, $elem);
            } else {
                call_user_method($func, $this, $xp, $elem);
            }
        }
    }

    /**
    *
    * @abstract
    */
    function StartHandler($xp, $elem, &$attribs)
    {
        return NULL;
    }


    /**
    *
    * @abstract
    */
    function EndHandler($xp, $elem)
    {
        return NULL;
    }


    // }}}
}

class XML_Parser_Error extends PEAR_Error {
    // {{{ properties

    var $error_message_prefix = 'XML_Parser: ';

    // }}}
    // {{{ constructor()

    function XML_Parser_Error($msgorparser = 'unknown error', $code = 0, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE) {

        if (is_resource($msgorparser)) {
            $code = xml_get_error_code($msgorparser);
            $msgorparser = sprintf("%s at XML input line %d",
                                   xml_error_string($code),
                                   xml_get_current_line_number($msgorparser));
        }
        $this->PEAR_Error($msgorparser, $code, $mode, $level);

    }

    // }}}
}
?>
