Current File : //opt/RZphp83/includes/CodeGen/XmlParser.php
<?php
/**
 * Yet another XML parsing class 
 *
 * PHP versions 5
 *
 * LICENSE: This source file is subject to version 3.0 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category   Tools and Utilities
 * @package    CodeGen
 * @author     Hartmut Holzgraefe <hartmut@php.net>
 * @copyright  2005-2008 Hartmut Holzgraefe
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    CVS: $Id: XmlParser.php,v 1.18 2007/04/26 11:06:36 hholzgra Exp $
 * @link       http://pear.php.net/package/CodeGen
 */

/**
 * Yet another XML parsing class 
 *
 * This is similar to the "func" mode of XML_Parser but it borrows
 * some concepts from DSSSL.
 * The tag handler method to call is not only determined by the tag
 * name but also potentially by the name of its parent tags, and the
 * most specific handler method (that is the one including the
 * maximum number of matching parent tags in its name) wins.
 * This way it is possible to have e.g. tagstart_name as a general
 * handler for a <name> tag while tagstart_function_name handles the
 * more special case of a <name> tag within a <function> tag.
 * Character data within a tag is collected and passed to the end
 * tag handler.
 * Tag names and attributes are managed using stack arrays.
 * Attributes are not only passed to both the start and end tag 
 * handlers.
 *
 * @category   Tools and Utilities
 * @package    CodeGen
 * @author     Hartmut Holzgraefe <hartmut@php.net>
 * @copyright  2005-2008 Hartmut Holzgraefe
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: @package_version@
 * @link       http://pear.php.net/package/CodeGen
 */

class CodeGen_XmlParser 
{
    /**
     * XML parser resource
     *
     * @var resource
     */
    protected $parser = null;

    /**
     * Parser stack for <include> management
     *
     * @var array
     */
    protected $parserStack = array();

    /**
     * We collect cData in here
     *
     * @var    string
     */
    protected $data = "";

    /**
     * We also try to remember where cData started
     *
     * @var    int
     */
    protected $dataLine = 0;

    /** 
     * We maintain the current tag nesting structure here
     *
     * @var    array
     */
    protected $tagStack = array();

    /** 
     * We also need to maintain a stack for tag aliases
     *
     * @var    array
     */
    protected $tagAliasStack = array();

    /** 
     * We keep track of tag attributes so that we can also provide them to the end tag handlers
     *
     * @var    array
     */
    protected $attrStack = array();

    /**
     * There is no clean way to terminate parsing from within a handler ...
     *
     * @var    bool
     */
    protected $error = false;

    /**
     * Input Filename
     * 
     * @var    string
     */
    protected $filename = false;

    /**
     * Input stream
     *
     * @var    resource
     */
    protected $fp = null;

    /**
     * Verbatim indicator
     *
     * @var    string
     */
    protected $verbatim = false;

    /**
     * Verbatim taglevel depth
     *
     * @var string
     */
    protected $verbatimDepth = 0;

    /**
     * Tag aliases 
     *
     */
    protected $tagAliases = array();

    /**
     * The constructor 
     *
     */
    function __construct()
    { 
        $this->parser = $this->newParser();
    }

    /**
     * Create a new SAX parser and associate it with this XmlParser instance
     *
     * @void
     */
    protected function newParser()
    {
        $parser = @xml_parser_create_ns(null, ' ');

        if (is_resource($parser)) {
            xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);

            xml_set_object($parser, $this);
            xml_set_element_handler($parser, 'startHandler', 'endHandler');
            xml_set_character_data_handler($parser, 'cDataHandler');
            xml_set_external_entity_ref_handler($parser, 'extEntityHandler');
            xml_set_processing_instruction_handler($parser, 'piHandler');
        }

        return $parser;
    }

    /**
     * Push current SAX parser instance to the parser stack
     *
     * @void
     */
    protected function pushParser()
    {
        if ($this->parser) {
            $entry = array($this->parser, $this->filename, $this->fp);    
            array_push($this->parserStack, $entry);
        }
    }

    /**
     * Replace current SAX parser with one popped from the parser stack
     *
     * @void
     */
    function popParser()
    {
        xml_parser_free($this->parser);
        list($this->parser, $this->filename, $this->fp) = array_pop($this->parserStack);
    }

    /**
     * Generate current parse position as string for error messages
     *
     * @void
     * @returns string
     */
    protected function posString()
    {
        return "in {$this->filename} on line ".
            xml_get_current_line_number($this->parser).
            ":".
            xml_get_current_column_number($this->parser);
    }
        
    /**
     * Create a new SAX parser to parse an external entity reference
     *
     * @void
     */
    protected function extEntityHandler($parser, $openEntityNames, $base, $systemId, $publicId) 
    {
        $this->pushParser();
        $this->parser = $this->newParser();
        $stat = $this->setInputFile($systemId);
        if ($stat) {
            $this->parse();
        } else {
            $this->error = PEAR::raiseError("Can't open system entity file '$systemId' ".$this->posString());
        }
        $this->popParser();
        return;
    }

    /**
     * Set file to parse
     *
     * @param string
     */
    public function setInputFile($filename) 
    {
        $this->filename = $filename;
            
        $this->fp = @fopen($filename, "r");

        return is_resource($this->fp);
    }

    /**
     * Perform the actual parsing
     *
     * @return boolean true on success
     */
    public function parse() 
    {
        if (!is_resource($this->parser)) {
            return PEAR::raiseError("Can't create XML parser");
        }

        if (!is_resource($this->fp)) {
            return PEAR::raiseError("No valid input file");
        }


        while (($data = fread($this->fp, 4096))) {
            if (!xml_parse($this->parser, $data, feof($this->fp))) {
                $this->error = PEAR::RaiseError(xml_error_string(xml_get_error_code($this->parser))." ".$this->posString());
            }
            if ($this->error) {
                return $this->error;
            }
        }

        $stat = $this->error ? $this->error : true;

        return $stat;
    }

    /**
     * Start verbatim mode
     *
     */
    protected function verbatim()
    {
        $this->verbatim = true;
        $this->verbatimDepth = 1;
    }

    /**
     * Try to find best matching tag handler for current tag nesting
     * 
     * @param  string  handler method prefix
     * @return string  hndler method name or false if no handler found
     */
    protected function findHandler($prefix)
    {
        for ($tags = $this->tagAliasStack; count($tags); array_shift($tags)) {
            $method = "{$prefix}_".join("_", $tags);
            if (method_exists($this, $method)) {
                return $method;
            }
        }

        return false;
    }


    /**
     * Try to find a tagstart handler for this tag and call it
     *
     * @param  resource internal parser handle
     * @param  string   tag name
     * @param  array    tag attributes         
     */
    protected function startHandler($XmlParser, $fulltag, $attr)
    {
        if ($this->error) return;

        $pos = strrpos($fulltag, " ");
            
        $ns  = $pos ? substr($fulltag, 0, $pos)  : "";
        $tag = $pos ? substr($fulltag, $pos + 1) : $fulltag;

        // XInclude handling
        if ($ns === "http://www.w3.org/2001/XInclude") {
            // TODO better error checking
            if ($tag === "include") {
                $path = isset($attr['href']) ? $attr['href'] : $attr['http://www.w3.org/2001/XInclude href'];

                if (isset($attr["parse"]) && $attr["parse"] == "text") {
                    if (is_readable($path)) {
                        $data = file_get_contents($path);
                        $this->cDataHandler($XmlParser, $data);
                    } else {
                        $this->error = PEAR::raiseError("Can't open XInclude file '$path' ".$this->posString());
                    }
                } else {
                    $this->pushParser();
                    $this->parser = $this->newParser();
                    $stat = $this->setInputFile($path);
                    if ($stat) {
                        $this->parse();
                    } else {
                        $this->error = PEAR::raiseError("Can't open XInclude file '$path' ".$this->posString());
                    }
                    $this->popParser();
                }
            }
                
            return;
        }

        // this *has* to be done *after* XInclude processing !!!
        array_push($this->tagStack, $tag);
        array_push($this->tagAliasStack, isset($this->tagAliases[$tag]) ? $this->tagAliases[$tag] : $tag);
        array_push($this->attrStack, $attr);

        if ($this->verbatim) {
            $this->verbatimDepth++;
            $this->data .= "<$tag";
            foreach ($attr as $key => $value) {
                $this->data .= " $key='$value'";
            }
            $this->data .= ">";
            return;
        }

        $this->data = "";
        $this->dataLine = xml_get_current_line_number($XmlParser);

        $method = $this->findHandler("tagstart");
        if ($method) {
            $err = $this->$method($attr);
            if (PEAR::isError($err)) {
                $this->error = $err;
                $this->error->addUserInfo($this->posString());
            }
        } else if (!$this->findHandler("tagend")) {
            $this->error = PEAR::raiseError("no matching tag handler for ".join(":", $this->tagStack));
            $this->error->addUserInfo($this->posString());              
        }
    }

    /**
     * Try to find a tagend handler for this tag and call it
     *
     * @param  resource internal parser handle
     * @param  string   tag name
     */
    protected function endHandler($XmlParser, $fulltag)
    {
        if ($this->error) return;

        $pos = strrpos($fulltag, " ");
            
        $ns  = $pos ? substr($fulltag, 0, $pos)  : "";
        $tag = $pos ? substr($fulltag, $pos + 1) : $fulltag;

        // XInclude handling
        if ($ns === "http://www.w3.org/2001/XInclude") {
            return;
        }

        // this *has* to be done *before* popping the tag stack!!!
        $method = $this->findHandler("tagend");

        $oldTag   = array_pop($this->tagStack);
        $oldAlias = array_pop($this->tagAliasStack);
        $attr     = array_pop($this->attrStack);

        if ($this->verbatim) {
            if (--$this->verbatimDepth > 0) {
                $this->data .= "</$tag>";
                return;
            } else {
                $this->verbatim = false;
            }               
        }

        if ($method) {
            $err = $this->$method($attr, $this->data, $this->dataLine, $this->filename);
            if (PEAR::isError($err)) {
                $this->error = $err;
                $this->error->addUserInfo($this->posString());                                   
            }
        }
        $this->data = "";
        $this->dataLine = 0;
    }


    /**
     * Just collect cData for later use in tag end handlers
     *
     * @param  resource internal parser handle
     * @param  string   cData to collect
     */
    protected function cDataHandler($XmlParser, $data)
    {
        $this->data.= $this->verbatim ? htmlspecialchars($data) : $data;
    }

    /**
     * Delegate processing instructions
     *
     * @param  resource internal parser handle
     * @param  string   PI name
     * @param  string   PI content data
     */
    protected function piHandler($XmlParser, $name, $data)
    {
        $methodName = $name."PiHandler";

        if (method_exists($this, $methodName)) {
            $this->$methodName($XmlParser, $data);
        } else {
            $this->error = PEAR::raiseError("unknown processing instruction '<$name'");
        }
    }

    /**
     * Tread <?data PI sections like <![CDATA[
     *
     * @param  resource internal parser handle
     * @param  string   cData to collect
     */
    protected function dataPiHandler($XmlParser, $data) 
    {
        $this->cDataHandler($XmlParser, $data);
    }

    /**
     * A helper stack for collecting stuff 
     *
     * @var    array
     */
    protected $helperStack = array();

    /**
     * The current helper (top of stack) 
     *
     * @var    mixed
     */
    protected $helper = false;

    /**
     * The previous helper (top-1 of stack) 
     *
     * @var    mixed
     */
    protected $helperPrev = false;
        
    /**
     * Push something on the helper stack
     *
     * @param mixed
     */
    protected function pushHelper($helper)
    {
        array_push($this->helperStack, $this->helper);
        $this->helperPrev = $this->helper;
        $this->helper = $helper;
    }


    /**
     * Pop something from the helper stack
     *
     */
    protected function popHelper()
    {
        // TODO add optional expectedType parameter?

        $oldHelper = $this->helper;

        $this->helper = array_pop($this->helperStack);
        if (count($this->helperStack)) {
            end($this->helperStack);
            $this->helperPrev = current($this->helperStack); 
        } else {
            $this->helperPrev = false;
        }

        return $oldHelper;
    }

        
    /**
     * Convert various boolean value representation
     *
     * @param  mixed  value
     * @param  string optional attribute name string for error messages
     * @return bool
     */
    protected function toBool($arg, $name="")
    {
        if (is_bool($arg)) {
            return $arg;
        }

        if (is_numeric($arg)) {
            switch ($arg) {
            case 0:
            case 1:
                return (bool)$arg;                
            }
        }

        if (is_string($arg)) {
            switch (strtolower($arg)) {
            case 'on':
            case 'yes':
            case 'true':
                return true;
            case 'off':
            case 'no':
            case 'false':
                return false;
            }
        }
                
        return PEAR::raiseError("'$arg' is not a valid value for boolean attribute $attribute");
    }


    /**
     * Check that no attributes are given
     *
     */
    protected function noAttributes($attr) 
    {
        if (!empty($attr)) {
            return PEAR::raiseError("<".end($this->tagStack)."> does not allow any attributes");
        }

        return true;
    }

    /**
     * Check attributes
     *
     * @param array   actual attribute/value pairs
     * @param array  optinal attribute names with default values
     * @param array required attribute names
     */
    protected function checkAttributes(&$attr, $optional, $required = array())
    {
        // check for missing required attributes
        foreach ($required as $key) {
            if (!isset($attr[$key])) {
                return PEAR::raiseError("required attribute '$key' missing in <".end($this->tagStack)."> ");
            }
        }

        // add defaults for missing optional arguments
        foreach ($optional as $key => $value) {
            if (is_numeric($key)) {
                $key   = $value;
                $value = null;
            }
            if (!isset($attr[$key])) {
                $attr[$key] = $value;
            }
        }

        // check for unknown attributes
        foreach ($attr as $key => $value) {
            if (!in_array($key, $required) && !in_array($key, $optional)) {
                return PEAR::raiseError("'$key' is not a valid attribute for <".end($this->tagStack)."> ");
            }
        }
        return true;
    }

    /**
     * Add a tag alias
     *
     * @param string  Tag name 
     * @param string  Alias for this tag
     */
    function addTagAlias($tag, $alias)
    {
        $this->tagAliases[$tag] = $alias;
    }
}

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * indent-tabs-mode:nil
 * End:
 */