<?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-2007 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 * * @category ToolsAndUtilities * @package phpDocumentor * @subpackage Parsers * @author Gregory Beaver <cellog@php.net> * @copyright 2003-2007 Gregory Beaver * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version CVS: $Id: TutorialHighlightParser.inc 246148 2007-11-14 01:57:04Z ashnazg $ * @tutorial tags.example.pkg, tags.filesource.pkg * @link http://www.phpdoc.org * @link http://pear.php.net/PhpDocumentor * @since 1.3.0 * @todo CS cleanup - change package to PhpDocumentor * @todo CS cleanup - PHPCS needs to ignore CVS Id length */ /** * Highlights source code using {@link parse()} * * @category ToolsAndUtilities * @package phpDocumentor * @subpackage Parsers * @author Gregory Beaver <cellog@php.net> * @copyright 2003-2007 Gregory Beaver * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version Release: 1.4.3 * @link http://www.phpdoc.org * @link http://pear.php.net/PhpDocumentor * @todo CS cleanup - change package to PhpDocumentor * @todo CS cleanup - change classname to PhpDocumentor_* */ 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', ); /**#@-*/ /** * advances output to a new line * * @return void * @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 * * @param int $num the line number * * @return void */ 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. * * @param string $parse_data blah * @param Converter &$converter blah * @param false|string $filesourcepath full path to file with @filesource tag, * if this is a @filesource parse * @param false|integer $linenum starting line number from * {@}source linenum} * * @staticvar integer used for recursion limiting if a handler for * an event is not found * @return bool * @uses setupStates() initialize parser state variables * @uses configWordParser() pass $parse_data to prepare retrieval of tokens * @todo CS cleanup - unable to get function signature below 85char wide */ 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 (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"; //DEBUG echo "LINE: " . $this->_line . "\n"; //DEBUG 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. * * @param string|array $word token value * @param integer $pevent parser event from {@link Parser.inc} * * @return void * @access private */ /** * Most tokens only need highlighting, and this method handles them * * @todo CS cleanup - PHPCS needs to recognize docblock template tags */ 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 * * @todo CS cleanup - PHPCS needs to recognize docblock template tags */ 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. * * @param string $word the output to add * @param bool $preformatted whether or not its preformatted * * @return void * @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 integer $e an event number * * @return void * @see WordParser */ function configWordParser($e) { $this->_wp->setSeperator($this->tokens[($e + 100)]); } /**#@+ * @param string|array $word token value * @param integer $pevent parser event from {@link Parser.inc} * * @return mixed returns false, or the event number */ /** * This function checks whether parameter $word is a token * for pushing a new event onto the Event Stack. * * @todo CS cleanup - PHPCS needs to recognize docblock template tags */ 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. * * @todo CS cleanup - PHPCS needs to recognize docblock template tags */ 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 bool|string $parsedata true if we are highlighting an inline {@}source} * tag's output, or the name of class we are going * to start from * * @return void * @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('}'); /**************************************************************/ } /** * searches for a parser event name based on its number * * @param int $value the event number * * @return string|int the event name, or the original value */ 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); ?>