<?php
//
// +----------------------------------------------------------------------+
// | PHP version 4.0                                                      |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2001 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.               |
// +----------------------------------------------------------------------+
// | Authors: Stig Bakken <ssb@fast.no>                                   |
// |                                                                      |
// +----------------------------------------------------------------------+
//

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
*/
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;

    // }}}
    // {{{ 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":
                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) {
        
        if (method_exists($this, $elem)) {
            call_user_method($elem, $this, $xp, $elem, &$attribs);
        }
        
    }

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

    function funcEndHandler($xp, $elem)
    {
        $func = $elem . '_';
        if (method_exists($this, $func)) {
            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);
        
    }

    // }}}
}
?>
