<?php
/**
 * Source Code Highlighting
 *
 * The classes in this file are responsible for the dynamic @example, and
 * <programlisting role="tutorial"> tags output.  Using the
 * WordParser, the phpDocumentor_TutorialHighlightParser
 * retrieves PHP tokens one by one from the array generated by
 * {@link WordParser} source retrieval functions
 * and then highlights them individually.
 *
 * It accomplishes this highlighting through the assistance of methods in
 * the output Converter passed to its parse() method, and then returns the
 * fully highlighted source as a string
 * 
 * phpDocumentor :: automatic documentation generator
 * 
 * PHP versions 4 and 5
 *
 * Copyright (c) 2003-2006 Gregory Beaver
 * 
 * LICENSE:
 * 
 * This library is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation;
 * either version 2.1 of the License, or (at your option) any
 * later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @package    phpDocumentor
 * @subpackage Parsers
 * @author     Gregory Beaver <cellog@php.net>
 * @copyright  2003-2006 Gregory Beaver
 * @license    http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @version    CVS: $Id: TutorialHighlightParser.inc,v 1.4 2006/05/01 04:13:51 cellog Exp $
 * @tutorial   tags.example.pkg, tags.filesource.pkg
 * @link       http://www.phpdoc.org
 * @link       http://pear.php.net/PhpDocumentor
 * @since      1.3.0
 */

/**
 * Highlights source code using {@link parse()}
 * @package phpDocumentor
 * @subpackage Parsers
 */
class phpDocumentor_TutorialHighlightParser extends Parser
{
    /**#@+ @access private */
    /**
     * Highlighted source is built up in this string
     * @var string
     */
    var $_output;
    /**
     * contents of the current source code line as it is parsed
     * @var string
     */
    var $_line;
    /**
     * Used to retrieve highlighted tokens
     * @var Converter a descendant of Converter
     */
    var $_converter;
    /**
     * Path to file being highlighted, if this is from a @filesource tag
     * @var false|string full path
     */
    var $_filesourcepath;
    /**
     * @var array
     */
    var $eventHandlers = array(
                                TUTORIAL_EVENT_NOEVENTS => 'defaultHandler',
                                TUTORIAL_EVENT_ITAG => 'defaultHandler',
                                TUTORIAL_EVENT_ATTRIBUTE => 'attrHandler',
                                TUTORIAL_EVENT_OPENTAG => 'defaultHandler',
                                TUTORIAL_EVENT_CLOSETAG => 'defaultHandler',
                                TUTORIAL_EVENT_ENTITY => 'defaultHandler',
                                TUTORIAL_EVENT_COMMENT => 'defaultHandler',
                                TUTORIAL_EVENT_SINGLEQUOTE => 'defaultHandler',
                                TUTORIAL_EVENT_DOUBLEQUOTE => 'defaultHandler',
    );
    /**#@-*/
    
    /**
     * @uses Converter::SourceLine() encloses {@link $_line} in a
     *                               converter-specific format
     */
    function newLineNum()
    {
        $this->_line .= $this->_converter->flushHighlightCache();
        $this->_output .= $this->_converter->SourceLine($this->_pv_curline + 1, $this->_line, $this->_path);
        $this->_line = '';
    }
    
    /**
     * Start the parsing at a certain line number
     */
    function setLineNum($num)
    {
        $this->_wp->linenum = $num;
    }
    
    /**
     * Parse a new file
     *
     * The parse() method is a do...while() loop that retrieves tokens one by
     * one from the {@link $_event_stack}, and uses the token event array set up
     * by the class constructor to call event handlers.
     *
     * The event handlers each process the tokens passed to them, and use the
     * {@link _addoutput()} method to append the processed tokens to the
     * {@link $_line} variable.  The word parser calls {@link newLineNum()}
     * every time a line is reached.
     *
     * In addition, the event handlers use special linking functions
     * {@link _link()} and its cousins (_classlink(), etc.) to create in-code
     * hyperlinks to the documentation for source code elements that are in the
     * source code.
     *
     * @uses setupStates() initialize parser state variables
     * @uses configWordParser() pass $parse_data to prepare retrieval of tokens
     * @param    string
     * @param    Converter
     * @param    false|string full path to file with @filesource tag, if this
     *           is a @filesource parse
     * @param    false|integer starting line number from {@}source linenum}
     * @staticvar    integer    used for recursion limiting if a handler for
     *                          an event is not found
     * @return    bool
     */
    function parse ($parse_data, &$converter, $filesourcepath = false, $linenum = false)
    {
        static $endrecur = 0;
        $parse_data = str_replace(array("\r\n", "\t"), array("\n", '    '), $parse_data);
        $this->_converter = &$converter;
        $converter->startHighlight();
        $this->_path = $filesourcepath;
        $this->setupStates($parse_data);

        $this->configWordParser(TUTORIAL_EVENT_NOEVENTS);
        if ($linenum !== false) $this->setLineNum($linenum);
        // initialize variables so E_ALL error_reporting doesn't complain
        $pevent = 0;
        $word = 0;

        do
        {
            $lpevent = $pevent;
            $pevent = $this->_event_stack->getEvent();
            if ($lpevent != $pevent)
            {
                $this->_last_pevent = $lpevent;
                $this->configWordParser($pevent);
            }
            $this->_wp->setWhitespace(true);

            $dbg_linenum = $this->_wp->linenum;
            $dbg_pos = $this->_wp->getPos();
            $this->_pv_last_word = $word;
            $this->_pv_curline = $this->_wp->linenum;
            $word = $this->_wp->getWord();

            if (0)//PHPDOCUMENTOR_DEBUG == true)
            {
                echo "LAST: ";
                echo "|" . $this->_pv_last_word;
                echo "|\n";
                echo "PEVENT: " . $this->getParserEventName($pevent) . "\n";
                echo "LASTPEVENT: " . $this->getParserEventName($this->_last_pevent) . "\n";
//                echo "LINE: ".$this->_line."\n";
//                echo "OUTPUT: ".$this->_output."\n";
                echo $dbg_linenum.'-'.$dbg_pos . ": ";
                echo '|'.htmlspecialchars($word);
                echo "|\n";
                echo "-------------------\n\n\n";
                flush();
            }
            if (isset($this->eventHandlers[$pevent]))
            {
                $handle = $this->eventHandlers[$pevent];
                $this->$handle($word, $pevent);
            } else
            {
                debug('WARNING: possible error, no handler for event number '.$pevent);
                if ($endrecur++ == 25)
                {
                    die("FATAL ERROR, recursion limit reached");
                }
            }
        } while (!($word === false));
        if (strlen($this->_line)) $this->newLineNum();
        return $this->_output;
    }
    
    /**#@+
     * Event Handlers
     *
     * All Event Handlers use {@link checkEventPush()} and
     * {@link checkEventPop()} to set up the event stack and parser state.
     * @access private
     * @param string|array token value
     * @param integer parser event from {@link Parser.inc}
     */
    /**
     * Most tokens only need highlighting, and this method handles them
     */
    function defaultHandler($word, $pevent)
    {
        if ($word == "\n") {
            $this->newLineNum();
            return;
        }
        if ($this->checkEventPush($word, $pevent)) {
            $this->_wp->backupPos($word);
            return;
        }
        $this->_addoutput($word);
        $this->checkEventPop($word, $pevent);
    }

    /**
     * Most tokens only need highlighting, and this method handles them
     */
    function attrHandler($word, $pevent)
    {
        if ($word == "\n") {
            $this->newLineNum();
            return;
        }
        if ($e = $this->checkEventPush($word, $pevent)) {
            if ($e == TUTORIAL_EVENT_SINGLEQUOTE || $e == TUTORIAL_EVENT_DOUBLEQUOTE) {
                $this->_addoutput($word);
            }
            return;
        }
        if ($this->checkEventPop($word, $pevent)) {
            $this->_wp->backupPos($word);
            return;
        }
        $this->_addoutput($word);
    }
    
    /**#@+
     * Output Methods
     * @access private
     */
    /**
     * This method adds output to {@link $_line}
     *
     * If a string with variables like "$test this" is present, then special
     * handling is used to allow processing of the variable in context.
     * @see _flush_save()
     */
    function _addoutput($word, $preformatted = false)
    {
        $type =
        array(
            TUTORIAL_EVENT_ATTRIBUTE => 'attribute',
            TUTORIAL_EVENT_SINGLEQUOTE => 'attributevalue',
            TUTORIAL_EVENT_DOUBLEQUOTE => 'attributevalue',
            TUTORIAL_EVENT_CLOSETAG => 'closetag',
            TUTORIAL_EVENT_ENTITY => 'entity',
            TUTORIAL_EVENT_ITAG => 'itag',
            TUTORIAL_EVENT_OPENTAG => 'opentag',
            TUTORIAL_EVENT_COMMENT => 'comment',
        );
        $a = $this->_event_stack->getEvent();
        if (in_array($a, array_keys($type))) {
            $this->_line .= $this->_converter->highlightTutorialSource($type[$a], $word);
        } else {
            $this->_line .= $this->_converter->flushHighlightCache();
            $this->_line .= $this->_converter->postProcess($word);
        }
    }
    /**#@-*/

    /**
     * tell the parser's WordParser {@link $wp} to set up tokens to parse words by.
     * tokens are word separators.  In English, a space or punctuation are examples of tokens.
     * In PHP, a token can be a ;, a parenthesis, or even the word "function"
     * @param    $value integer an event number
     * @see WordParser
     */
    
    function configWordParser($e)
    {
        $this->_wp->setSeperator($this->tokens[($e + 100)]);
    }
    /**
     * this function checks whether parameter $word is a token for pushing a new event onto the Event Stack.
     * @return mixed    returns false, or the event number
     */
    
    function checkEventPush($word,$pevent)
    {
        $e = false;
        if (isset($this->pushEvent[$pevent]))
        {
            if (isset($this->pushEvent[$pevent][strtolower($word)]))
            $e = $this->pushEvent[$pevent][strtolower($word)];
        }
        if ($e)
        {
            $this->_event_stack->pushEvent($e);
            return $e;
        } else {
            return false;
        }
    }

    /**
     * this function checks whether parameter $word is a token for popping the current event off of the Event Stack.
     * @return mixed    returns false, or the event number popped off of the stack
     */
    
    function checkEventPop($word,$pevent)
    {
        if (!isset($this->popEvent[$pevent])) return false;
        if (in_array(strtolower($word),$this->popEvent[$pevent]))
        {
            return $this->_event_stack->popEvent();
        } else {
            return false;
        }
    }

    /**
     * Initialize all parser state variables
     * @param boolean true if we are highlighting an inline {@}source} tag's
     *                output
     * @param false|string name of class we are going to start from
     * @uses $_wp sets to a new {@link phpDocumentor_HighlightWordParser}
     */
    function setupStates($parsedata)
    {
        $this->_output = '';
        $this->_line = '';
        unset($this->_wp);
        $this->_wp = new WordParser;
        $this->_wp->setup($parsedata);
        $this->_event_stack = new EventStack;
        $this->_event_stack->popEvent();
        $this->_event_stack->pushEvent(TUTORIAL_EVENT_NOEVENTS);
        $this->_pv_linenum = null;
        $this->_pv_next_word = false;
    }

    /**
     * Initialize the {@link $tokenpushEvent, $wordpushEvent} arrays
     */
    function phpDocumentor_TutorialHighlightParser()
    {
        $this->allowableInlineTags = $GLOBALS['_phpDocumentor_inline_tutorial_tags_allowed'];
        $this->inlineTagHandlers = array('*' => 'handleDefaultInlineTag');
        $this->tokens[STATE_TUTORIAL_NOEVENTS]        = array("\n",'{@', '<!--', '</', '<', '&');
        $this->tokens[STATE_TUTORIAL_ITAG]        = array("\n","}");
        $this->tokens[STATE_TUTORIAL_OPENTAG]        = array("\n","\t"," ", '>', '/>');
        $this->tokens[STATE_TUTORIAL_CLOSETAG]        = array("\n",'>');
        $this->tokens[STATE_TUTORIAL_COMMENT]        = array("\n",'-->');
        $this->tokens[STATE_TUTORIAL_ENTITY]        = array("\n",';');
        $this->tokens[STATE_TUTORIAL_ATTRIBUTE]        = array("\n",'"',"'",'>','/>');
        $this->tokens[STATE_TUTORIAL_DOUBLEQUOTE]        = array("\n",'"','&','{@');
        $this->tokens[STATE_TUTORIAL_SINGLEQUOTE]        = array("\n","'",'&','{@');
/**************************************************************/

        $this->pushEvent[TUTORIAL_EVENT_NOEVENTS] = 
            array(
                "{@" => TUTORIAL_EVENT_ITAG,
                '<' => TUTORIAL_EVENT_OPENTAG,
                '</' => TUTORIAL_EVENT_CLOSETAG,
                '&' => TUTORIAL_EVENT_ENTITY,
                '<!--' => TUTORIAL_EVENT_COMMENT,
            );
/**************************************************************/

        $this->pushEvent[TUTORIAL_EVENT_OPENTAG] = 
            array(
                " " => TUTORIAL_EVENT_ATTRIBUTE,
                "\n" => TUTORIAL_EVENT_ATTRIBUTE,
            );
/**************************************************************/

        $this->pushEvent[TUTORIAL_EVENT_ATTRIBUTE] = 
            array(
                "'" => TUTORIAL_EVENT_SINGLEQUOTE,
                '"' => TUTORIAL_EVENT_DOUBLEQUOTE,
            );
/**************************************************************/

        $this->pushEvent[TUTORIAL_EVENT_SINGLEQUOTE] = 
            array(
                '&' => TUTORIAL_EVENT_ENTITY,
                '{@' => TUTORIAL_EVENT_ITAG,
            );
/**************************************************************/

        $this->pushEvent[TUTORIAL_EVENT_DOUBLEQUOTE] = 
            array(
                '&' => TUTORIAL_EVENT_ENTITY,
                '{@' => TUTORIAL_EVENT_ITAG,
            );
/**************************************************************/

        $this->popEvent[TUTORIAL_EVENT_ENTITY] = array(';');
/**************************************************************/

        $this->popEvent[TUTORIAL_EVENT_SINGLEQUOTE] = array("'");
/**************************************************************/

        $this->popEvent[TUTORIAL_EVENT_DOUBLEQUOTE] = array('"');
/**************************************************************/

        $this->popEvent[TUTORIAL_EVENT_OPENTAG] = array('>', '/>');
/**************************************************************/

        $this->popEvent[TUTORIAL_EVENT_CLOSETAG] = array('>');
/**************************************************************/

        $this->popEvent[TUTORIAL_EVENT_COMMENT] = array('-->');
/**************************************************************/

        $this->popEvent[TUTORIAL_EVENT_ATTRIBUTE] = array('>','/>');
/**************************************************************/

        $this->popEvent[TUTORIAL_EVENT_ITAG] = array('}');
/**************************************************************/
    }

    function getParserEventName ($value)
    {    
        $lookup = array(
            TUTORIAL_EVENT_NOEVENTS         => "TUTORIAL_EVENT_NOEVENTS",
            TUTORIAL_EVENT_ITAG         => "TUTORIAL_EVENT_ITAG",
            TUTORIAL_EVENT_OPENTAG         => "TUTORIAL_EVENT_OPENTAG",
            TUTORIAL_EVENT_ATTRIBUTE         => "TUTORIAL_EVENT_ATTRIBUTE",
            TUTORIAL_EVENT_CLOSETAG         => "TUTORIAL_EVENT_CLOSETAG",
            TUTORIAL_EVENT_ENTITY         => "TUTORIAL_EVENT_ENTITY",
            TUTORIAL_EVENT_COMMENT         => "TUTORIAL_EVENT_COMMENT",
            TUTORIAL_EVENT_SINGLEQUOTE         => "TUTORIAL_EVENT_SINGLEQUOTE",
            TUTORIAL_EVENT_DOUBLEQUOTE         => "TUTORIAL_EVENT_DOUBLEQUOTE",
        );
        if (isset($lookup[$value]))
        return $lookup[$value];
        else return $value;
    }
}


/** starting state */
define("TUTORIAL_EVENT_NOEVENTS"    ,    1);
/** currently in starting state */
define("STATE_TUTORIAL_NOEVENTS"    ,    101);

/** used when an {@}inline tag} is found */
define("TUTORIAL_EVENT_ITAG"    ,    2);
/** currently parsing an {@}inline tag} */
define("STATE_TUTORIAL_ITAG"    ,    102);

/** used when an open <tag> is found */
define("TUTORIAL_EVENT_OPENTAG"    ,    3);
/** currently parsing an open <tag> */
define("STATE_TUTORIAL_OPENTAG"    ,    103);

/** used when a <tag attr="attribute"> is found */
define("TUTORIAL_EVENT_ATTRIBUTE"    ,    4);
/** currently parsing an open <tag> */
define("STATE_TUTORIAL_ATTRIBUTE"    ,    104);

/** used when a close </tag> is found */
define("TUTORIAL_EVENT_CLOSETAG"    ,    5);
/** currently parsing a close </tag> */
define("STATE_TUTORIAL_CLOSETAG"    ,    105);

/** used when an &entity; is found */
define("TUTORIAL_EVENT_ENTITY"    ,    6);
/** currently parsing an &entity; */
define("STATE_TUTORIAL_ENTITY"    ,    106);

/** used when a <!-- comment --> is found */
define("TUTORIAL_EVENT_COMMENT"    ,    7);
/** currently parsing a <!-- comment --> */
define("STATE_TUTORIAL_COMMENT"    ,    107);

/** used when a <!-- comment --> is found */
define("TUTORIAL_EVENT_SINGLEQUOTE"    ,    8);
/** currently parsing a <!-- comment --> */
define("STATE_TUTORIAL_SINGLEQUOTE"    ,    108);

/** used when a <!-- comment --> is found */
define("TUTORIAL_EVENT_DOUBLEQUOTE"    ,    9);
/** currently parsing a <!-- comment --> */
define("STATE_TUTORIAL_DOUBLEQUOTE"    ,    109);
?>
