* @copyright 2002-2008 Gregory Beaver * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version CVS: $Id: HighlightParser.inc 253641 2008-02-24 02:35:44Z ashnazg $ * @filesource * @link http://www.phpdoc.org * @link http://pear.php.net/PhpDocumentor * @tutorial tags.example.pkg, tags.filesource.pkg, tags.inlinesource.pkg * @since 1.2.0beta3 * @todo CS cleanup - change package to PhpDocumentor */ /** * Retrieve tokens from an array of tokens organized by line numbers * * @category ToolsAndUtilities * @package phpDocumentor * @subpackage Parsers * @author Gregory Beaver * @copyright 2002-2008 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 * @since 1.2.0beta3 * @todo CS cleanup - change package to PhpDocumentor * @todo CS cleanup - change class name to PhpDocumentor_* */ class phpDocumentor_HighlightWordParser extends phpDocumentorTWordParser { /** * Hash used to keep track of line numbers that have already been initialized * @var array * @access private */ var $_listLineNums = array(); /** * Initialize the parser object * * @param array &$input the input * @param phpDocumentor_HighlightParser &$parser the parser * * @return void */ function setup(&$input, &$parser) { $this->_parser = &$parser; $this->data = &$input; $this->_all = $input; $this->_sourceline = 0; $this->pos = 0; $this->linenum = 0; } /** * debugging function * * @return void * @access private */ function printState() { $linenum = $this->linenum; $pos = $this->pos; if (!isset($this->_all[$this->linenum][$this->pos])) { $linenum++; $pos = 0; } $details = ''; $token = $this->_all[$linenum][$pos]; if (is_array($token)) { $details = token_name($token[0]); $token = htmlspecialchars($token[1]); } else { $token = htmlspecialchars($token); } debug('Next Token ' . $this->linenum . '-' . $this->pos . ':' . $details); var_dump($token); } /** * Retrieve the position of the next token that will be parsed * in the internal token array * * @return array format: array(line number, position) */ function nextToken() { $linenum = $this->linenum; $pos = $this->pos; if (!isset($this->_all[$this->linenum][$this->pos])) { $linenum++; $pos = 0; } if (!isset($this->_all[$linenum][$pos])) { return false; } return array($linenum, $pos); } /** * Retrieve the next token * * @return array|string either array(PHP token constant, token) or string * non-specific separator */ function getWord() { if (!isset($this->_all[$this->linenum][$this->pos])) { $this->linenum++; $this->pos = 0; if (!isset($this->_all[$this->linenum])) { return false; } $this->_parser->newLineNum(); return $this->getWord(); } $word = $this->_all[$this->linenum][$this->pos++]; return str_replace("\t", ' ', $word); } /** * back the word parser to the previous token as defined by $last_token * * @param array|string $last_token token, or output from {@link nextToken()} * @param bool $is_pos if true, backupPos interprets $last_token * to be the position in the internal token * array of the last token * * @return void */ function backupPos($last_token, $is_pos = false) { if (!$last_token) { return; } if ($is_pos) { $this->linenum = $last_token[0]; $this->pos = $last_token[1]; return; } if ($last_token === false) { return; } //fancy_debug('before', $this->linenum, $this->pos, // token_name($this->_all[$this->linenum][$this->pos][0]), // htmlentities($this->_all[$this->linenum][$this->pos][1]), // $this->_all[$this->linenum]); do { $this->pos--; if ($this->pos < 0) { $this->linenum--; if ($this->linenum < 0) { var_dump($last_token); break; } $this->pos = count($this->_all[$this->linenum]) - 1; } } while (!$this->tokenEquals($last_token, str_replace("\t", ' ', $this->_all[$this->linenum][$this->pos]))); //fancy_debug('after', $this->linenum, $this->pos, // token_name($this->_all[$this->linenum][$this->pos][0]), // htmlentities($this->_all[$this->linenum][$this->pos][1])); } } /** * Highlights source code using {@link parse()} * * @category ToolsAndUtilities * @package phpDocumentor * @subpackage Parsers * @author Gregory Beaver * @copyright 2002-2008 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 * @since 1.2.0beta3 * @todo CS cleanup - change package to PhpDocumentor * @todo CS cleanup - change class name to PhpDocumentor_* */ class phpDocumentor_HighlightParser extends phpDocumentorTParser { /**#@+ * @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( PARSER_EVENT_ARRAY => 'defaultHandler', PARSER_EVENT_CLASS => 'handleClass', PARSER_EVENT_COMMENT => 'handleComment', PARSER_EVENT_DOCBLOCK_TEMPLATE => 'handleDocBlockTemplate', PARSER_EVENT_END_DOCBLOCK_TEMPLATE => 'handleEndDocBlockTemplate', PARSER_EVENT_LOGICBLOCK => 'handleLogicBlock', PARSER_EVENT_METHOD_LOGICBLOCK => 'handleMethodLogicBlock', PARSER_EVENT_NOEVENTS => 'defaultHandler', PARSER_EVENT_OUTPHP => 'defaultHandler', PARSER_EVENT_CLASS_MEMBER => 'handleClassMember', PARSER_EVENT_DEFINE => 'defaultHandler', PARSER_EVENT_DEFINE_PARAMS => 'defaultHandler', PARSER_EVENT_DEFINE_PARAMS_PARENTHESIS => 'defaultHandler', PARSER_EVENT_INCLUDE_PARAMS_PARENTHESIS => 'defaultHandler', PARSER_EVENT_DOCBLOCK => 'handleDocBlock', PARSER_EVENT_TAGS => 'handleTags', PARSER_EVENT_DESC => 'handleDesc', PARSER_EVENT_DOCKEYWORD => 'handleTag', PARSER_EVENT_DOCKEYWORD_EMAIL => 'handleDockeywordEmail', PARSER_EVENT_EOFQUOTE => 'handleQuote', PARSER_EVENT_FUNCTION => 'handleFunction', PARSER_EVENT_METHOD => 'handleMethod', PARSER_EVENT_FUNCTION_PARAMS => 'handleFunctionParams', PARSER_EVENT_FUNC_GLOBAL => 'handleFuncGlobal', PARSER_EVENT_INLINE_DOCKEYWORD => 'handleInlineDockeyword', PARSER_EVENT_INCLUDE => 'defaultHandler', PARSER_EVENT_INCLUDE_PARAMS => 'defaultHandler', PARSER_EVENT_QUOTE => 'handleQuote', PARSER_EVENT_QUOTE_VAR => 'handleQuoteVar', PARSER_EVENT_PHPCODE => 'handlePhpCode', PARSER_EVENT_SINGLEQUOTE => 'handleSingleQuote', PARSER_EVENT_STATIC_VAR => 'defaultHandler', PARSER_EVENT_STATIC_VAR_VALUE => 'defaultHandler', PARSER_EVENT_VAR => 'handleVar', ); /** * event handlers for @tags * @tutorial tags.pkg */ var $tagHandlers = array( '*' => 'defaultTagHandler', 'abstract' => 'coreTagHandler', 'access' => 'coreTagHandler', 'author' => 'coreTagHandler', 'category' => 'coreTagHandler', 'copyright' => 'coreTagHandler', 'deprecated' => 'coreTagHandler', 'example' => 'coreTagHandler', 'filesource' => 'coreTagHandler', 'final' => 'coreTagHandler', 'global' => 'globalTagHandler', 'ignore' => 'coreTagHandler', 'license' => 'coreTagHandler', 'link' => 'coreTagHandler', 'name' => 'coreTagHandler', 'package' => 'coreTagHandler', 'param' => 'paramTagHandler', 'parameter' => 'paramTagHandler', 'see' => 'coreTagHandler', 'since' => 'coreTagHandler', 'subpackage' => 'coreTagHandler', 'internal' => 'coreTagHandler', 'return' => 'returnTagHandler', 'static' => 'coreTagHandler', 'staticvar' => 'staticvarTagHandler', 'throws' => 'coreTagHandler', 'todo' => 'coreTagHandler', 'tutorial' => 'coreTagHandler', 'uses' => 'coreTagHandler', 'var' => 'varTagHandler', 'version' => 'coreTagHandler', 'property' => 'propertyTagHandler', 'property-read' => 'propertyTagHandler', 'property-write' => 'propertyTagHandler', 'method' => 'propertyTagHandler' ); /**#@-*/ /** * wraps the current line (via the converter) and resets it to empty * * @return void * @uses Converter::SourceLine() encloses {@link $_line} in a * converter-specific format */ function newLineNum() { if ($this->_pf_no_output_yet) { return; } $this->_flush_save(); $this->_line .= $this->_converter->flushHighlightCache(); $this->_output .= $this->_converter->SourceLine($this->_wp->linenum, $this->_line, $this->_path); $this->_line = ''; } /** * Start the parsing at a certain line number * * @param int $num 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 array &$parse_data the parse data * @param Converter &$converter the converter object * @param bool $inlinesourceparse whether this data is from an * inline {@}source} tag * @param string|false $class if a string, it is the name of the * class whose method we are parsing * containing a {@}source} tag * @param false|integer $linenum starting line number from * {@}source linenum} * @param false|string $filesourcepath full path to file with @filesource * tag, if this is a @filesource parse * * @staticvar int 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 - rename tokenizer_ext constant to uppercase */ function parse (&$parse_data, &$converter, $inlinesourceparse = false, $class = false, $linenum = false, $filesourcepath = false) { if (!tokenizer_ext) { if (is_array($parse_data)) { $parse_data = join($parse_data, ''); } $parse_data = explode("\n", $parse_data); $this->_output = ''; foreach ($parse_data as $linenum => $line) { if ($linenum > 0) { $this->_output .= $converter->SourceLine($linenum, $line, $filesourcepath); } } return $converter->PreserveWhiteSpace($this->_output); } static $endrecur = 0; $this->_converter = &$converter; $converter->startHighlight(); $this->_path = $filesourcepath; $this->setupStates($inlinesourceparse, $class); $this->configWordParser($parse_data); 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; } if ($pevent == PARSER_EVENT_CLASS_MEMBER) { $this->_wp->setWhitespace(true); } else { $this->_wp->setWhitespace(false); } if (!is_array($word)) { $lw = $word; } if (is_array($word) && $word[0] != T_WHITESPACE) { $lw = $word; } $dbg_linenum = $this->_wp->linenum; $dbg_pos = $this->_wp->getPos(); $word = $this->_wp->getWord(); if (is_array($word) && ($word[0] == T_WHITESPACE || $word[0] == T_COMMENT) && $pevent != PARSER_EVENT_CLASS_MEMBER ) { //debug("added " . $this->_wp->linenum . '-' . $this->_wp->pos); $this->_addoutput($word); continue; } else { $this->_pv_last_word = $lw; } if ($pevent != PARSER_EVENT_DOCBLOCK) { $this->_pv_last_next_word = $this->_pv_next_word; $this->_pv_next_word = $this->_wp->nextToken(); } // in wordparser, have to keep track of lines //$this->publishEvent(PHPDOCUMENTOR_EVENT_NEWLINENUM, // $this->_wp->linenum); if (PHPDOCUMENTOR_DEBUG == true) { echo "LAST: "; if (is_array($this->_pv_last_word)) { echo token_name($this->_pv_last_word[0]) . ' => |' . htmlspecialchars($this->_pv_last_word[1]); } else { 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 . ": "; if (is_array($word)) { echo token_name($word[0]) . ' => |' . htmlspecialchars($word[1]); } else { echo '|'.htmlspecialchars($word); } echo "|\n"; $this->_wp->printState(); echo "NEXT TOKEN: "; $tok1 = $this->_pv_next_word; $tok = $this->_wp->_all[$tok1[0]][$tok1[1]]; if (is_array($tok)) { echo token_name($tok[0]) . ' => ' . $tok1[0] . '-' . $tok1[1] . '|' . htmlspecialchars($tok[1]); } else { echo "|" . $tok; } echo "|\n"; echo "-------------------\n\n\n"; flush(); } if ($word !== false && isset($this->eventHandlers[$pevent])) { $handle = $this->eventHandlers[$pevent]; $this->$handle($word, $pevent); } elseif ($word !== false) { 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 int $pevent parser event from {@link Parser.inc} * * @return void * @access private */ /** * Most tokens only need highlighting, and this method handles them */ function defaultHandler($word, $pevent) { $this->_addoutput($word); if ($this->checkEventPush($word, $pevent)) { return; } $this->checkEventPop($word, $pevent); } /** * Handles global declarations in a function, like: * * * function foobar() * { * global $_phpDocumentor_setting; * } * * * @uses _globallink() instead of _addoutput(), to link to global variables * if they are used in a function */ function handleFuncGlobal($word, $pevent) { if ($this->checkEventPush($word, $pevent)) { return; } $this->_globallink($word); $this->checkEventPop($word, $pevent); } /** * Handles strings in quotation marks and heredoc * * Special handling is needed for strings that contain variables like: * * $a = "$test string" * * The tokenizer parses out tokens '"',array(T_VARIABLE,'$test'),' string', * and '"'. Since it is possible to have $this->classvar in a string, * we save a variable name just in case the next token is -> to allow linking * to class members. Otherwise, the string is simply highlighted. * * constant strings (with no $variables in them) are passed as a single * entity, and so will be saved in the last token parsed. This means the * event handler must tell the word parser to re-retrieve the current token * so that the correct event handler can process it. */ function handleQuote($word, $pevent) { if ($this->_pf_inmethod && is_array($word) && $word[0] == T_VARIABLE) { $this->_pv_lastvar = $word; } if ($this->checkEventPush($word, $pevent)) { $this->_addoutput($word); return; } if ($this->_pf_quote_active && (($this->_pv_last_word == '"' && $this->_last_pevent != PARSER_EVENT_QUOTE) || (is_array($this->_pv_last_word) && $this->_pv_last_word[0] == T_END_HEREDOC && $this->_last_pevent != PARSER_EVENT_EOFQUOTE)) ) { $this->_pf_quote_active = false; $this->_wp->backupPos($word); $this->_event_stack->popEvent(); return; } if (!$this->_pf_quote_active && (($this->_pv_last_word == '"' && $this->_last_pevent != PARSER_EVENT_QUOTE) || (is_array($this->_pv_last_word) && $this->_pv_last_word[0] == T_END_HEREDOC && $this->_last_pevent != PARSER_EVENT_EOFQUOTE)) ) { if (is_array($word) && $word[0] == T_VARIABLE) { $this->_pv_lastvar = $word; } $this->_pf_quote_active = true; $this->_save_highlight_state = $this->_converter->getHighlightState(); $this->_converter->startHighlight(); $this->_addoutput($word); $this->checkEventPop($word, $pevent); return; } elseif (is_array($this->_pv_last_word) && $this->_pv_last_word[0] == T_CONSTANT_ENCAPSED_STRING ) { //$this->_pv_quote_data = $this->_pv_last_word[1]; $this->_event_stack->popEvent(); $this->_wp->backupPos($word); return; } if ($this->checkEventPop($word, $pevent)) { $this->_pf_quote_active = false; } $this->_addoutput($word); } /** * Handles {$variable} within a "quote" * * This is a simple handler, for a very complex * array of legal syntax. It is legal to nest control structures * inside the {}, and other weird stuff. */ function handleQuoteVar($word, $pevent) { if ($this->checkEventPop($word, $pevent)) { $this->_pf_quote_active = true; $this->_addoutput($word); return; } if ($this->_pf_inmethod && is_array($word) && $word[0] == T_VARIABLE) { $this->_pv_lastvar = $word; } if ($this->checkEventPush($word, $pevent)) { $this->_pf_quote_active = false; if (is_string($word) && ($word == '{' || $word == '"' || $word == "'") ) { $this->_pf_quote_active = true; $this->_pv_lastvar = false; } } $this->_addoutput($word); } /** * Handles define() statements * * The only thing this handler cares about is retrieving the name of the * define variable, and the end of the define statement, so after the name * is found, it simply makes sure parentheses are matched as in this case: * * * define("test",array("hello",6 => 4, 5 => array('there'))); * * * This handler and the DEFINE_PARAMS_PARENTHESIS handler (which is just * {@link defaultHandler()} in this version, as nothing fancy is needed) * work together to ensure proper parenthesis matching. * * If the define variable is documented, a link will be created to its * documentation using the Converter passed. */ function handleDefine($word, $pevent) { static $token_save; if (!isset($token_save)) { $token_save = array(); } $e = $this->checkEventPush($word, $pevent); if ($e && $e != PARSER_EVENT_DEFINE_PARAMS_PARENTHESIS) { return; } if (!isset($this->_pv_define_params_data)) { $this->_pv_define_params_data = ''; } if ($this->checkEventPop($word, $pevent)) { unset($token_save); $this->_addoutput($word); } if ($this->_pf_definename_isset) { $this->_addoutput($word); } else { if ($word != ",") { $token_save[] = $word; if (is_array($word)) { $word = $word[1]; } $this->_pv_define_params_data .= $word; } else { if (substr($this->_pv_define_params_data, 0, 1) == substr($this->_pv_define_params_data, strlen($this->_pv_define_params_data) - 1) && in_array(substr($this->_pv_define_params_data, 0, 1), array('"', "'")) ) { // remove leading and ending quotation marks // if there are only two $a = substr($this->_pv_define_params_data, 0, 1); $b = substr($this->_pv_define_params_data, 1, strlen($this->_pv_define_params_data) - 2); if (strpos($b, $a) === false) { $this->_pv_define_params_data = $b; } } $this->_pf_definename_isset = true; $link = $this->_converter->getLink($this->_pv_define_params_data); foreach ($token_save as $token) { if (is_object($link)) { if (is_array($token)) { $token = $token[1]; } $this->_addoutput($this->_converter->returnSee($link, $token)); } else { $this->_addoutput($save, $token); } } $this->_pv_define_params_data = ''; } } } /** * Handles normal global code. Special consideration is taken for DocBlocks * as they need to retrieve the whole DocBlock before doing any output, so * the parser flag {@link $_pf_no_output_yet} is set to tell * {@link _addoutput()} not to spit anything out yet. * * @uses _link() make any global code that is a documentable element link * to the php manual or its documentation */ function handlePhpCode($word, $pevent) { $test = $this->checkEventPush($word, $pevent); if ($test == PARSER_EVENT_DOCBLOCK || $test == PARSER_EVENT_COMMENT) { if (substr($word[1], 0, 2) == '/*' && strpos($word[1], '*/')) { $this->_pv_last_word = $word; if ($word[1] == '/**#@-*/') { $this->_pf_docblock_template = true; } else { $this->_pf_docblock = true; } return $this->handleDocBlock($word, PARSER_EVENT_DOCBLOCK); } $this->_pf_no_output_yet = true; $this->_pv_saveline = $this->_wp->linenum + 1; return; } if (is_array($word) && $word[0] == T_DOUBLE_COLON) { $this->_pf_colon_colon = true; } if (!$this->_pf_colon_colon && is_array($word) && $word[0] == T_STRING) { $this->_pv_last_string = $word; } $this->_link($word); $this->checkEventPop($word, $pevent); } /** * Handle the function declaration header * * This handler only sees the "function name" portion of the function * declaration. Handling of the function parameters is by * {@link handleFunctionParams()}, and the function body is handled by * {@link handleLogicBlock()} */ function handleFunction($word, $pevent) { if ($this->checkEventPush($word, $pevent)) { $this->_addoutput($word); return; } if ($this->checkEventPop($word, $pevent)) { return; } $this->_link($word); } /** * Handle the method declaration header * * This handler only sees the "function name" portion of the method * declaration. Handling of the method parameters is by * {@link handleFunctionParams()}, and the method body is handled by * {@link handleMethodLogicBlock()} */ function handleMethod($word, $pevent) { if ($this->checkEventPush($word, $pevent)) { $this->_addoutput($word); return; } if ($this->checkEventPop($word, $pevent)) { if ($word == ';') { $this->_addoutput($word); } return; } $this->_methodlink($word); } /** * Handler for the stuff between ( and ) in a function declaration * * * function handles($only,$these,$parameters){...} * */ function handleFunctionParams($word, $pevent) { if ($this->checkEventPush($word, $pevent)) { $this->_addoutput($word); return; } $this->_addoutput($word); $this->checkEventPop($word, $pevent); } /** * Handler for function body. * * The function body is checked for php functions, documented constants, * functions, and indirectly for global statements. It hyperlinks to the * documentation for detected elements is created. Everything else is * highlighted normally. */ function handleLogicBlock($word, $pevent) { if ($this->checkEventPush($word, $pevent)) { $this->_addoutput($word); return; } if (is_array($word) && $word[0] == T_DOUBLE_COLON) { $this->_pf_colon_colon = true; } if (!$this->_pf_colon_colon && is_array($word) && $word[0] == T_STRING) { $this->_pv_last_string = $word; } $this->_link($word); if ($this->checkEventPop($word, $pevent)) { $e = $this->_event_stack->popEvent(); $this->_event_stack->pushEvent($e); if ($e == PARSER_EVENT_FUNCTION) { $this->_wp->backupPos($word); } } } /** * Handler for method body. * * Like functions, the method body is checked for php functions, documented * constants, functions, and indirectly for global statements. It also * checks for "$this->XXXX" where XXXX is a class variable or method, and * links to the documentation for detected elements is created. Everything * else is highlighted normally. */ function handleMethodLogicBlock($word, $pevent) { if (isset($this->_pv_prev_var_type)) { //debug('prevtype is set'); if (!is_array($word)) { unset($this->_pv_prev_var_type); } else { if ($word[0] != T_WHITESPACE && $word[0] != T_STRING && $word[0] != T_OBJECT_OPERATOR ) { //fancy_debug('unset', $word); unset($this->_pv_prev_var_type); } } } $this->_pf_inmethod = true; if ($e = $this->checkEventPush($word, $pevent)) { $this->_addoutput($word); if ($e == PARSER_EVENT_CLASS_MEMBER) { $this->_pf_no_output_yet = true; } return; } if (is_array($word) && $word[0] == T_DOUBLE_COLON) { $this->_pf_colon_colon = true; } if (!$this->_pf_colon_colon && is_array($word) && $word[0] == T_STRING) { $this->_pv_last_string = $word; } if (is_array($word) && $word[0] == T_VARIABLE) { $this->_pv_lastvar = $word; } $this->_link($word); if ($this->checkEventPop($word, $pevent)) { $this->_pf_inmethod = false; $e = $this->_event_stack->popEvent(); $this->_event_stack->pushEvent($e); if ($e == PARSER_EVENT_METHOD) { $this->_wp->backupPos($word); } } } /** * Handles $obj->classmember in a method body * * This handler is responsible for linking to the documentation of a * class member when it is used directly in a method body. * * There are two methods of determining whether to link: * - $this->member * - $this->member->submember * * The first case is handled by the $_pv_lastvar variable, and the * second case is handled by the $_pv_prev_var_type variable. $_pv_lastvar * is always set to the value of the last T_VARIABLE token, if and only if * no text has occurred between the variable and a T_OBJECT_OPERATOR token * "->". handleClassMember will only link if the last variable encountered * was $this. * * When $this->variable is encountered, the variable is looked up to see * if it can be found, and if so, the contents of its @var tag are processed * to see if the member variable is defined to have 1 and only 1 class. * If so, the $_pv_prev_var_type variable is set to this classname. When * submember is processed, the HighlightParser checks to see if * $_pv_prev_var_type::submember() or $_pv_prev_var_type::$submember exists, * and if it does, it is linked to. */ function handleClassMember($word, $pevent) { if (!isset($this->_pv_lastvar) && !isset($this->_pv_prev_var_type)) { //fancy_debug('returned from', $word, $this->_pv_prev_var_type); $this->_pf_no_output_yet = false; $this->_event_stack->popEvent(); return $this->defaultHandler($word, $pevent); } if (isset($this->_pv_cm_name)) { $this->_pf_obj_op = false; $name = $this->_pv_cm_name; unset($this->_pv_cm_name); //debug('unset pvcmname'); $this->_event_stack->popEvent(); // control variable for _pv_prev_var_type $setnow = false; if ((isset($this->_pv_lastvar) && $this->_pv_lastvar[1] == '$this') || isset($this->_pv_prev_var_type) ) { if (is_array($word) && $word[0] == T_WHITESPACE) { // preserve value of _pv_prev_var_type $setnow = true; $save = $this->_wp->nextToken(); $temp = $this->_wp->getWord(); $this->_wp->backupPos($save, true); } if ((is_string($word) && $word == '(') || (isset($temp) && is_string($temp) && $temp == '(') ) { // it's a function $this->_pf_no_output_yet = false; $this->_methodlink($name); unset($this->_pv_prev_var_type); } else { // it's a variable //fancy_debug('name is ', $name); $this->_pf_no_output_yet = false; $this->_varlink($name, true); $templink = $this->_converter->getLink('object ' . $this->_pv_class); $class = false; if (is_object($templink)) { $class = $this->_converter->classes ->getClass($templink->name, $templink->path); } if ($class) { $varname = $name; if (is_array($varname)) { $varname = $name[1]; } if ($varname{0} != '$') { $varname = '$'.$varname; } $var = $class->getVar($this->_converter, $varname); if (is_object($var) && $var->docblock->var) { $type = $var->docblock->var->returnType; } if (isset($type)) { if (strpos($type, 'object') === false) { $type = 'object '.$type; } $type = $this->_converter->getLink($type); if (phpDocumentor_get_class($type) == 'classlink') { // the variable's type is a class, // save it for future -> //fancy_debug('set prev_var_type!', $type->name); $setnow = true; $this->_pv_prev_var_type = $type->name; } else { unset($this->_pv_prev_var_type); } } else { unset($this->_pv_prev_var_type); } } else { unset($this->_pv_prev_var_type); } } } else { $this->_pf_no_output_yet = false; // this does NewLinenum if necessary $this->_wp->backupPos($word); $this->_wp->getWord(); $this->_addoutput($name); } if (!$setnow) { //debug('unset prevtype, no setnow'); unset($this->_pv_prev_var_type); } unset($this->_pv_lastvar); $this->_pf_no_output_yet = false; // this does NewLinenum if necessary $this->_wp->backupPos($word); $this->_wp->getWord(); if ($word[0] == T_OBJECT_OPERATOR) { $this->_wp->backupPos($word); } else { $this->_addoutput($word); } return; } if (!$this->_pf_obj_op && is_array($this->_pv_last_word) && $this->_pv_last_word[0] == T_OBJECT_OPERATOR ) { if ((isset($this->_pv_lastvar) && $this->_pv_lastvar[1] == '$this') || isset($this->_pv_prev_var_type) ) { $this->_pf_obj_op = true; } else { $this->_pf_no_output_yet = false; // this does NewLinenum if necessary $this->_wp->backupPos($word); $this->_wp->getWord(); $this->_addoutput($word); $this->_event_stack->popEvent(); } } if (is_array($word) && $word == T_WHITESPACE) { $this->_pf_no_output_yet = false; // this does NewLinenum if necessary $this->_wp->backupPos($word); $this->_wp->getWord(); $this->_addoutput($word); return; } if ($this->_pf_obj_op) { if (!(is_array($word) && ($word[0] == T_STRING || $word[0] == T_WHITESPACE)) ) { unset($this->_pv_lastvar); //debug('unset lastvar'); $this->_event_stack->popEvent(); $this->_pf_no_output_yet = false; // this does NewLinenum if necessary $this->_wp->backupPos($word); $this->_wp->getWord(); $this->_addoutput($word); return; } if ($word[0] == T_STRING) { //fancy_debug('set pvcmname to', $word); $this->_pv_cm_name = $word; } else { $this->_pf_no_output_yet = false; // this does NewLinenum if necessary $this->_wp->backupPos($word); $this->_wp->getWord(); $this->_addoutput($word); } } } /** * Handles comments * * Comments are almost always single-line tokens, and so will be * in the last word. This handler checks to see if the current token * is in fact a comment, and if it isn't, it backs up and returns control * to the parent event handler with that word. */ function handleComment($word, $pevent) { $w = $this->_pv_last_word; // don't perform this check if this is a normal comment. Docblocks // have the _pf_no_output_yet variable set to true if ($this->_pf_no_output_yet && is_array($w) && (in_array($w[0], array(T_COMMENT, T_DOC_COMMENT)) && strpos($w[1], '/**') === 0) ) { $this->_event_stack->popEvent(); $this->_event_stack->pushEvent(PARSER_EVENT_DOCBLOCK); return $this->handleDocBlock($word, PARSER_EVENT_DOCBLOCK); } if ($this->_pf_no_output_yet) { $flag = 1; $this->_pf_no_output_yet = false; $this->_addoutput($this->_pv_last_word); } if (!is_array($word) || !in_array($word[0], array(T_COMMENT, T_DOC_COMMENT)) || (in_array($word[0], array(T_COMMENT, T_DOC_COMMENT)) && strpos($word[1], '/**') === 0) ) { $this->_event_stack->popEvent(); if (strpos($this->_pv_last_word[1], "\n") !== false) { //$this->_wp->linenum++; //$this->newLineNum(); } $this->_wp->backupPos($this->_pv_last_word); $this->_wp->getWord(); //var_dump($this->_wp->nextToken()); return; } elseif (isset($flag)) { $this->newLineNum(); } $this->_addoutput($word); $this->checkEventPop($word, $pevent); if (strpos($word[1], '*/') === strlen($word[1]) - 2) { $this->_event_stack->popEvent(); } } /** * Handle class declarations * * Handles the initial declaration line: * * class X * * or * * class X extends Y implements I * * @uses _classlink() to link to documentation for X and for Y class in * "class X extends Y" */ function handleClass($word, $pevent) { $this->_pf_in_class = true; $a = $this->checkEventPush($word, $pevent); if (!isset($this->_pv_class) && is_array($word) && $word[0] == T_STRING) { $this->_pv_class = $this->_converter->class = $word[1]; $this->_classlink($word); return; } if (is_array($word) && in_array($word[0], array(T_PRIVATE, T_PROTECTED, T_PUBLIC)) ) { $starttok = $this->_wp->nextToken(); $test = array(T_WHITESPACE); while ($test && $test[0] == T_WHITESPACE) { $tok = $this->_wp->nextToken(); $test = $this->_wp->getWord(); } // while if (is_array($test) && $test[0] == T_VARIABLE) { $this->_wp->backupPos($tok, true); return; } $this->_wp->backupPos($starttok, true); } if (@in_array($this->_pv_last_word[0], array(T_PRIVATE, T_PROTECTED, T_PUBLIC)) ) { if (is_array($word) && $word[0] == T_VARIABLE) { $this->_wp->backupPos($this->_pv_last_word); $this->_event_stack->pushEvent(PARSER_EVENT_VAR); return; } } if ($this->_pf_extends_found && is_array($word) && $word[0] == T_STRING) { $this->_classlink($word); return; } if (is_array($word) && $word[0] == T_EXTENDS) { $this->_pf_extends_found = true; } if ($a == PARSER_EVENT_DOCBLOCK) { $this->_pf_no_output_yet = true; $this->_pv_saveline = $this->_wp->linenum + 1; return; } $this->_addoutput($word); if ($this->checkEventPop($word, $pevent)) { $this->_pf_in_class = false; unset($this->_pv_class); } } /** * Handles class variable declaration * * * class X * { * var $Y; * } * * * @uses _varlink() make a link to $Y documentation in class variable * declaration "var $Y;" */ function handleVar($word, $pevent) { if ($this->checkEventPush($word, $pevent)) { $this->_addoutput($word); return; } if (is_array($word) && $word[0] == T_VARIABLE) { return $this->_varlink($word); } $this->_addoutput($word); $this->checkEventPop($word, $pevent); } /** * This handler is responsible for highlighting DocBlocks * * handleDocBlock determines whether the docblock is normal or a template, * and gathers all the lines of the docblock together before doing any * processing * * As it is not possible to distinguish any comment token from a docblock * token, this handler is also called for comments, and will pass control * to {@link handleComment()} if the comment is not a DocBlock * * @uses commonDocBlock() once all lines of the DocBlock have been retrieved */ function handleDocBlock($word, $pevent) { if (!($this->_pf_docblock || $this->_pf_docblock_template)) { if (strpos($this->_pv_last_word[1], '/**') !== 0) { // not a docblock $this->_wp->backupPos($this->_pv_last_word); $this->_event_stack->popEvent(); $this->_event_stack->pushEvent(PARSER_EVENT_COMMENT); $this->_pf_no_output_yet = false; return; } else { $this->_pf_no_output_yet = true; $this->_pv_db_lines = array(); } } $last_word = $this->_pv_last_word[1]; $dtype = '_pv_docblock'; if ($last_word == '/**#@-*/') { // stop using docblock template $this->_pf_no_output_yet = false; $this->_addDocBlockoutput('closetemplate', $last_word); if ($this->_pv_next_word !== false) { $this->_wp->backupPos($this->_pv_next_word, true); } $this->_event_stack->popEvent(); return; } if (!($this->_pf_docblock || $this->_pf_docblock_template)) { $this->_pv_db_lines = array(); if (strpos($last_word, '/**#@+') === 0) { // docblock template definition $this->_pf_docblock_template = true; } else { $this->_pf_docblock = true; } $this->_pv_db_lines[] = $last_word; if (strpos($last_word, '*/') !== false) { $this->commonDocBlock(); return; } $this->_pv_db_lines[] = $word[1]; if (strpos($word[1], '*/') !== false) { $this->commonDocBlock(); } } else { $this->_pv_db_lines[] = $word[1]; } if (($this->_pf_docblock || $this->_pf_docblock_template) && (strpos($word[1], '*/') !== false) ) { $this->commonDocBlock(); } } /**#@-*/ /** * This continuation of handleDocBlock splits DocBlock comments up into * phpDocumentor tokens. It highlights DocBlock templates in a different * manner from regular DocBlocks, recognizes inline tags, regular tags, * and distinguishes between standard core tags and other tags, and * recognizes parameters to tags like @var. * * the type in "@var type description" will be highlighted as a php type, * and the var in "@param type $var description" will be highlighted as a * php variable. * * @return void * @uses handleDesc() highlight inline tags in the description * @uses handleTags() highlight all tags * @access private */ function commonDocBlock() { $this->_event_stack->popEvent(); $lines = $this->_pv_db_lines; $go = count($this->_pv_db_lines); for ($i=0; $i < $go; $i++) { if (substr(trim($lines[$i]), 0, 2) == '*/' || substr(trim($lines[$i]), 0, 1) != '*' && substr(trim($lines[$i]), 0, 3) != '/**' ) { $lines[$i] = array($lines[$i], false); } elseif (substr(trim($lines[$i]), 0, 3) == '/**') { $linesi = array(); // remove leading "/**" $linesi[1] = substr(trim($lines[$i]), 3); if (empty($linesi[1])) { $linesi[0] = $lines[$i]; } else { $linesi[0] = substr($lines[$i], 0, strpos($lines[$i], $linesi[1])); } $lines[$i] = $linesi; } else { $linesi = array(); // remove leading "* " $linesi[1] = substr(trim($lines[$i]), 1); if (empty($linesi[1])) { $linesi[0] = $lines[$i]; } else { $linesi[0] = substr($lines[$i], 0, strpos($lines[$i], $linesi[1])); } $lines[$i] = $linesi; } } for ($i = 0; $i < count($lines); $i++) { if ($lines[$i][1] === false) { continue; } if (substr(trim($lines[$i][1]), 0, 1) == '@' && substr(trim($lines[$i][1]), 0, 2) != '@ ' ) { $tagindex = $i; $i = count($lines); } } if (isset($tagindex)) { $tags = array_slice($lines, $tagindex); $desc = array_slice($lines, 0, $tagindex); } else { $tags = array(); $desc = $lines; } //var_dump($desc, $tags); $this->_pf_no_output_yet = false; $save = $this->_wp->linenum; $this->_wp->linenum = $this->_pv_saveline; $this->handleDesc($desc); $this->handleTags($tags); $this->_pv_db_lines = array(); $this->_wp->linenum = $save; if (strpos($this->_pv_last_word[1], '*/') !== false) { $this->_wp->backupPos($this->_pv_next_word, true); } $this->_pf_docblock = $this->_pf_docblock_template = false; } /** * Handle the description area of a DocBlock * * This method simply finds inline tags and highlights them * separately from the rest of the description. * * @param mixed $desc the description piece(s) * * @return void * @uses getInlineTags() * @access private */ function handleDesc($desc) { $dbtype = 'docblock'; $dbtype .= ($this->_pf_docblock ? '' : 'template'); foreach ($desc as $line) { $this->getInlineTags($line[0] . $line[1]); if (strpos($line[0], '*/') === false && !(substr($line[0], 0, 2) == '/*' && strpos($line[1], '*/') !== false) ) { $this->newLineNum(); $this->_wp->linenum++; } } if ($this->_pf_internal) { $this->_pf_internal = false; } } /** * Handle phpDocumentor tags in a DocBlock * * This method uses the {@link $tagHandlers} array to determine which * method will handle tags found in the docblock, and passes the data to * the individual handlers one by one * * @param array $tags array of tags to handle * * @return void * @access private */ function handleTags($tags) { $newtags = array(); $curtag = array(); for ($i=0; $i < count($tags); $i++) { $tagsi = trim($tags[$i][1]); if (substr($tagsi, 0, 1) == '@' && substr($tagsi, 0, 2) != '@ ') { // start a new tag $tags[$i][1] = array(substr($tags[$i][1], 0, strpos($tags[$i][1], $tagsi)), $tagsi); if (!empty($curtag)) { $newtags[] = $curtag; $curtag = array(); } $curtag[] = $tags[$i]; } else { $curtag[] = $tags[$i]; } } if (!empty($curtag)) { $newtags[] = $curtag; } foreach ($newtags as $tag) { foreach ($tag as $i => $t) { if ($t[1] === false) { continue; } if (is_array($t[1])) { $tag[$i][1][1] = explode(" ", str_replace("\t", ' ', $t[1][1])); $x = $tag[$i][1][1]; } } $tagname = substr(array_shift($x), 1); $restoftag = $tag; if (isset($this->tagHandlers[$tagname])) { $handle = $this->tagHandlers[$tagname]; } else { $handle = $this->tagHandlers['*']; } $this->$handle($tagname, $restoftag); } } /** * This handler recognizes all {@}inline} tags * * Normal inline tags are simply highlighted. the {@}internal}} inline * tag {@tutorial tags.inlineinternal.pkg} is highlighted differently * to distinguish it from other inline tags. * * @param mixed $value the tag value * @param bool $endinternal indicates the end of an @internal tag * * @return void * @access private */ function getInlineTags($value, $endinternal = false) { if (!$value) { return; } if ($this->_pf_internal && !$endinternal) { if (strpos($value, '}}') !== false) { $x = strrpos($value, '}}'); // add the rest of internal $this->getInlineTags(substr($value, 0, $x + 3), true); // strip internal from value $value = substr($value, strrpos($value, '}}') + 1); // turn off internal $this->_pf_internal = false; } } if (!$value) { return; } $dbtype = 'docblock'; $dbtype .= ($this->_pf_docblock ? '' : 'template'); $save = $value; $value = explode('{@', $value); $newval = array(); // everything before the first {@ is normal text $this->_addDocBlockoutput($dbtype, $value[0]); for ($i=1; $i < count($value); $i++) { if (substr($value[$i], 0, 1) == '}') { $this->_addDocBlockoutput($dbtype, '{@}' . substr($value[$i], 1)); } else { $save = $value[$i]; $value[$i] = str_replace("\t", " ", $value[$i]); $value[$i] = explode(" ", $value[$i]); $word = array_shift($value[$i]); $val = join(' ', $value[$i]); if ($word == 'internal') { $this->_pf_internal = true; $this->_addDocBlockoutput($dbtype, '{@internal '); $value[$i] = substr($save, strlen('internal') + 1); // strip internal and cycle as if it were normal text. $this->_addDocBlockoutput($dbtype, $value[$i]); continue; } if (in_array(str_replace('}', '', $word), $this->allowableInlineTags) ) { if (strpos($word, '}')) { $word = str_replace('}', '', $word); $val = '} ' . $val; } $val = explode('}', $val); if (count($val) == 1) { //addError(PDERROR_UNTERMINATED_INLINE_TAG, // $word, '', $save); } $rest = $val; $val = array_shift($rest); if ($endinternal) { $rest = join('}', $rest); } else { $rest = join(' ', $rest); } if (isset($this->inlineTagHandlers[$word])) { $handle = $this->inlineTagHandlers[$word]; } else { $handle = $this->inlineTagHandlers['*']; } $this->$handle($word, $val); $this->_addDocBlockoutput($dbtype, $rest); } else { $val = $word . ' ' . $val; $this->_addDocBlockoutput($dbtype, '{@' . $val); } } } } /** * Handles all inline tags * * @param string $name the tag name * @param mixed $value the tag value * * @return void * @access private */ function handleDefaultInlineTag($name, $value) { $this->_addDocBlockoutput('inlinetag', '{@' . $name . ' ' . $value . '}'); } /**#@+ * phpDocumentor DocBlock tag handlers * * @param string $name tag name * @param array $value array of lines contained in the tag description * * @return void * @access private */ /** * Handle normal tags * * This handler adds to outpu all comment information before the tag begins * as in " * " before "@todo" in " * @todo" * * Then, it highlights the tag as a regular or coretag based on $coretag. * Finally, it uses getInlineTags to highlight the description * * @param bool $coretag whether this tag is a core tag or not * * @uses getInlineTags() highlight a tag description */ function defaultTagHandler($name, $value, $coretag = false) { $dbtype = 'docblock'; $dbtype .= ($this->_pf_docblock ? '' : 'template'); foreach ($value as $line) { $this->_addDocBlockoutput($dbtype, $line[0]); if ($line[1] === false) { if (trim($line[0]) != '*/') { $this->newLineNum(); $this->_wp->linenum++; } continue; } $this->_addDocBlockoutput($dbtype, $line[1][0]); $stored = ''; if (is_array($line[1][1])) { foreach ($line[1][1] as $i => $tpart) { if ($tpart == '@' . $name && $i == 0) { $tagname = 'tag'; if ($coretag) { $tagname = 'coretag'; } $this->_addDocBlockoutput($tagname, '@' . $name); continue; } $stored .= ' ' . $tpart; } } else { $stored = $line[1]; } $this->getInlineTags($stored); if (strpos($stored, '*/') === false) { $this->newLineNum(); $this->_wp->linenum++; } } } /** * main handler for "core" tags * * @see defaultTagHandler() */ function coreTagHandler($name, $value) { return $this->defaultTagHandler($name, $value, true); } /** * Handles @global * * This handler works like {@link defaultTagHandler()} except it highlights * the type and variable (if present) in "@global type $variable" or * "@global type description" */ function globalTagHandler($name, $value) { $this->paramTagHandler($name, $value); } /** * Handles @param * * This handler works like {@link defaultTagHandler()} except it highlights * the type and variable (if present) in "@param type $variable description" * or "@param type description" * * @param bool $checkforvar private parameter, checks for $var or not */ function paramTagHandler($name, $value, $checkforvar = true) { $dbtype = 'docblock'; $dbtype .= ($this->_pf_docblock ? '' : 'template'); $ret = $this->retrieveType($value, 0, $checkforvar); foreach ($value as $num => $line) { $this->_addDocBlockoutput($dbtype, $line[0]); if ($line[1] === false) { if (trim($line[0]) != '*/') { $this->newLineNum(); $this->_wp->linenum++; } continue; } $this->_addDocBlockoutput($dbtype, $line[1][0]); $stored = ''; $typeloc = 1; $varloc = 2; if (is_array($line[1][1])) { $this->_addDocBlockoutput('coretag', '@' . $name . ' '); foreach ($ret[0] as $text) { if (is_string($text)) { $this->_addDocBlockoutput($dbtype, $text); } if (is_array($text)) { if ($text[0] != 'desc') { $this->_addDocBlockoutput($text[0], $text[1]); } else { $stored .= $text[1]; } } } } else { if (isset($ret[$num])) { foreach ($ret[$num] as $text) { if (is_string($text)) { $this->_addDocBlockoutput($dbtype, $text); } if (is_array($text)) { if ($text[0] != 'desc') { $this->_addDocBlockoutput($text[0], $text[1]); } else { $stored .= $text[1]; } } } } else { $stored = $line[1]; } } $this->getInlineTags($stored); if (strpos($stored, '*/') === false) { $this->newLineNum(); $this->_wp->linenum++; } } } /** * handles the @staticvar tag * * @see paramTagHandler() */ function staticvarTagHandler($name, $value) { return $this->paramTagHandler($name, $value); } /** * handles the @var tag * * @see paramTagHandler() */ function varTagHandler($name, $value) { return $this->paramTagHandler($name, $value); } /** * Handles @return * * This handler works like {@link defaultTagHandler()} except it highlights * the type in "@return type description" */ function returnTagHandler($name, $value) { $this->paramTagHandler($name, $value, false); } /** * Handles @property(-read or -write) and @method magic tags */ function propertyTagHandler($name, $value) { return $this->paramTagHandler($name, $value, true); } /**#@-*/ /** * Retrieve the type portion of a @tag type description * * Tags like @param, @return and @var all have a PHP type portion in their * description. Since the type may contain the expression "object blah" * where blah is a classname, it makes parsing out the type field complex. * * Even more complicated is the case where a tag variable can contain * multiple types, such as object blah|object blah2|false, and so this * method handles these cases. * * @param array $value array of words that were separated by spaces * @param 0|1 $state 0 = find the type, 1 = find the var, if present * @param bool $checkforvar flag to determine whether to check for the end of a * type is defined by a $varname * * @return array Format: array(state (0 [find type], 1 [var], 2 [done]), * @access private */ function retrieveType($value, $state = 0, $checkforvar = false) { $index = 0; $result = array(); do { if (!isset($value[$index][1])) { return $result; } $val = $value[$index][1]; if (empty($val)) { return $result; } if ($index == 0) { $val = $val[1]; array_shift($val); } else { $val = explode(' ', $val); } $ret = $this->_retrieveType($val, $state, $checkforvar); $state = $ret[0]; $result[$index++] = $ret[1]; } while ((!$checkforvar && $state < 1) || ($state < 2 && $checkforvar)); return $result; } /** * used by {@link retrieveType()} in its work * * @param array $value array of words that were separated by spaces * @param 0|1 $state 0 = find the type, 1 = find the var, if present * @param bool $checkforvar flag to determine whether to check for the end of a * type is defined by a $varname * * @return array * @access private */ function _retrieveType($value, $state, $checkforvar) { $result = array(); $result[] = $this->_removeWhiteSpace($value, 0); if ($state == 0) { if (!count($value)) { return array(2, $result); } $types = ''; $index = 0; if (trim($value[0]) == 'object') { $result[] = array('tagphptype', $value[0] . ' '); $types .= array_shift($value).' '; $result[] = $this->_removeWhiteSpace($value, 0); if (!count($value)) { // was just passed "object" return array(2, $result); } if ($value[0]{0} == '$' || substr($value[0], 0, 2) == '&$') { // was just passed "object" // and the next thing is a variable name if ($checkforvar) { $result[] = array('tagvarname' , $value[0] . ' '); array_shift($value); } $result[] = array('desc', join(' ', $value)); return array(2, $result); } } $done = false; $loop = -1; do { // this loop checks for type|type|type and for // type|object classname|type|object classname2 if (strpos($value[0], '|')) { $temptypes = explode('|', $value[0]); while (count($temptypes)) { $type = array_shift($temptypes); $result[] = array('tagphptype', $type); if (count($temptypes)) { $result[] = '|'; } } if (trim($type) == 'object') { $result[] = array('tagphptype', $types . ' '); $result[] = $this->_removeWhiteSpace($value, 0); } else { $done = true; } array_shift($value); if (count($value) && strlen($value[0]) && isset ($value[0]) && ($value[0]{0} == '$' || substr($value[0], 0, 2) == '&$') ) { // was just passed "object" // and the next thing is a variable name $result[] = array('tagvarname' , $value[0] . ' '); array_shift($value); $result[] = array('desc', join(' ', $value)); return array(2, $result); } } else { $result[] = array('tagphptype', $value[0] . ' '); array_shift($value); $done = true; } $loop++; } while (!$done && count($value)); if ($loop) { $result[] = ' '; } // still searching for type if (!$done && !count($value)) { return array(0, $result); } // still searching for var if ($done && !count($value)) { return array(1, $result); } } $result[] = $this->_removeWhiteSpace($value, 0); $state = 1; if ($checkforvar) { if (count($value)) { $state = 2; if (substr($value[0], 0, 1) == '$' || substr($value[0], 0, 2) == '&$' ) { $result[] = array('tagvarname' , $value[0] . ' '); array_shift($value); } } else { $state = 1; } } $result[] = array('desc', join(' ', $value)); return array($state, $result); } /** * captures trailing whitespace * * @param array &$value array of string * @param int $index index to seek non-whitespace to * * @return string whitespace * @access private */ function _removeWhiteSpace(&$value, $index) { $result = ''; if (count($value) > $index && empty($value[$index])) { $found = false; for ($i = $index; $i < count($value) && !strlen($value[$i]); $i++) { $result .= ' '; } array_splice($value, $index, $i - $index); } return $result; } /**#@+ * Link generation methods * * @param string|array $word token to try to link * * @access private */ /** * Generate a link to documentation for an element * * This method tries to link to documentation for functions, methods, * PHP functions, class names, and if found, adds the links to output * instead of plain text */ function _link($word) { if (is_array($word) && $word[0] == T_STRING) { if ($this->_pf_colon_colon) { $this->_pf_colon_colon = false; $combo = $this->_pv_last_string[1] . '::' . $word[1] . '()'; //debug('testing ' . $combo); $link = $this->_converter->getLink($combo); if (is_object($link)) { $this->_addoutput($this->_converter->returnSee($link, $word[1]), true); return; } $this->_addoutput($word); return; } $link = $this->_converter->getLink($word[1] . '()'); if (is_object($link)) { $this->_addoutput($this->_converter->returnSee($link, $word[1]), true); return; } elseif (is_string($link) && strpos($link, 'ttp://')) { $this->_addoutput($this->_converter->returnLink($link, $word[1]), true); return; } else { $link = $this->_converter->getLink($word[1]); if (is_object($link)) { $word[1] = $this->_converter->returnSee($link, $word[1]); } $this->_addoutput($word, true); return; } } $this->_addoutput($word); } /** * Works like {@link _link()} except it only links to global variables */ function _globallink($word) { if (!is_array($word)) { return $this->_addoutput($word); } if ($word[0] != T_VARIABLE) { return $this->_addoutput($word); } if (is_array($word) && $word[0] == T_VARIABLE) { $link = $this->_converter->getLink('global ' . $word[1]); if (is_object($link)) { $this->_addoutput($this->_converter->returnSee($link, $word[1]), true); return; } } $this->_addoutput($word); } /** * Works like {@link _link()} except it only links to classes */ function _classlink($word) { //debug("checking class " . $word[1]); if (is_array($word) && $word[0] == T_STRING) { $link = $this->_converter->getLink($word[1]); if (is_object($link)) { $this->_addoutput($this->_converter->returnSee($link, $word[1]), true); return; } } $this->_addoutput($word); } /** * Works like {@link _link()} except it only links to methods */ function _methodlink($word) { if (is_array($word) && $word[0] == T_STRING) { //debug("checking method " . $this->_pv_class . '::' . $word[1] . '()'); if (isset($this->_pv_prev_var_type)) { $link = $this->_converter->getLink($this->_pv_prev_var_type . '::' . $word[1] . '()'); } else { $link = $this->_converter->getLink($this->_pv_class . '::' . $word[1] . '()'); } if (is_object($link)) { $this->_addoutput($this->_converter->returnSee($link, $word[1]), true); return; } if (isset($this->_pv_prev_var_type)) { $this->_addoutput($word); return; } //debug("checking method " . $word[1] . '()'); $link = $this->_converter->getLink($word[1] . '()'); if (is_object($link)) { $this->_addoutput($this->_converter->returnSee($link, $word[1]), true); return; } } $this->_addoutput($word); } /** * Works like {@link _link()} except it only links to class variables * * @param bool $justastring true if the $word is only a string */ function _varlink($word, $justastring=false) { if ($justastring) { $word[0] = T_VARIABLE; } if (is_array($word) && $word[0] == T_VARIABLE) { $x = ($justastring ? '$' : ''); //debug("checking var " . $this->_pv_class . '::' . $x . $word[1]); if (isset($this->_pv_prev_var_type)) { //debug("checking var " . $this->_pv_prev_var_type . '::' . // $x . $word[1]); $link = $this->_converter->getLink($this->_pv_prev_var_type . '::' . $x . $word[1]); } else { $link = $this->_converter->getLink($this->_pv_class . '::' . $x . $word[1]); } if (is_object($link)) { $this->_addoutput($this->_converter->returnSee($link, $word[1]), true); return; } //debug("checking var " . $x . $word[1]); if (isset($this->_pv_prev_var_type)) { $this->_addoutput($word); return; } $link = $this->_converter->getLink($x . $word[1]); if (is_object($link)) { $this->_addoutput($this->_converter->returnSee($link, $word[1]), true); 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 mixed $word the string|array tag token and value * @param bool $preformatted whether or not the $word is already formatted * * @return void * @see _flush_save() */ function _addoutput($word, $preformatted = false) { if ($this->_pf_no_output_yet) { return; } if ($this->_pf_quote_active) { if (is_array($word)) { $this->_save .= $this->_converter->highlightSource($word[0], $word[1]); } else { $this->_save .= $this->_converter->highlightSource(false, $word, true); } } else { $this->_flush_save(); if (is_string($word) && trim($word) == '') { $this->_line .= $this->_converter->postProcess($word); return; } if (is_array($word) && trim($word[1]) == '') { $this->_line .= $this->_converter->postProcess($word[1]); return; } if (is_array($word)) { $this->_line .= $this->_converter->highlightSource($word[0], $word[1], $preformatted); } else { $this->_line .= $this->_converter->highlightSource(false, $word, $preformatted); } } } /** * Like {@link _output()}, but for DocBlock highlighting * * @param mixed $dbtype the docblock type * @param mixed $word the string|array tag token and value * @param bool $preformatted whether or not the $word is already formatted * * @return void */ function _addDocBlockoutput($dbtype, $word, $preformatted = false) { if ($this->_pf_internal) { $this->_line .= $this->_converter->highlightDocBlockSource('internal', $word, $preformatted); } else { $this->_line .= $this->_converter->highlightDocBlockSource($dbtype, $word, $preformatted); } } /** * Flush a saved string variable highlighting * * {@source} * * @return void * @todo CS cleanup - rename to _flushSave() for camelCase rule */ function _flush_save() { if (!empty($this->_save)) { $this->_save .= $this->_converter->flushHighlightCache(); // clear the existing cache, reset it to the old value if (isset($this->_save_highlight_state)) { $this->_converter-> _setHighlightCache($this->_save_highlight_state[0], $this->_save_highlight_state[1]); } $this->_line .= $this->_converter-> highlightSource(T_CONSTANT_ENCAPSED_STRING, $this->_save, true); $this->_save = ''; } } /**#@-*/ /** * Give the word parser necessary data to begin a new parse * * @param array &$data all tokens separated by line number * * @return void */ function configWordParser(&$data) { $this->_wp->setup($data, $this); $this->_wp->setWhitespace(true); } /** * Initialize all parser state variables * * @param bool $inlinesourceparse true if we are highlighting an inline * {@}source} tag's output * @param false|string $class name of class we are going * to start from * * @return void * @uses $_wp sets to a new {@link phpDocumentor_HighlightWordParser} */ function setupStates($inlinesourceparse, $class) { $this->_output = ''; $this->_line = ''; unset($this->_wp); $this->_wp = new phpDocumentor_HighlightWordParser; $this->_event_stack = new EventStack; if ($inlinesourceparse) { $this->_event_stack->pushEvent(PARSER_EVENT_PHPCODE); if ($class) { $this->_event_stack->pushEvent(PARSER_EVENT_CLASS); $this->_pv_class = $class; } } else { $this->_pv_class = null; } $this->_pv_define = null; $this->_pv_define_name = null; $this->_pv_define_value = null; $this->_pv_define_params_data = null; $this->_pv_dtype = null; $this->_pv_docblock = null; $this->_pv_dtemplate = null; $this->_pv_func = null; $this->_pv_global_name = null; $this->_pv_global_val = null; $this->_pv_globals = null; $this->_pv_global_count = null; $this->_pv_include_params_data = null; $this->_pv_include_name = null; $this->_pv_include_value = null; $this->_pv_linenum = null; $this->_pv_periodline = null; $this->_pv_paren_count = 0; $this->_pv_statics = null; $this->_pv_static_count = null; $this->_pv_static_val = null; $this->_pv_quote_data = null; $this->_pv_function_data = null; $this->_pv_var = null; $this->_pv_varname = null; $this->_pf_definename_isset = false; $this->_pf_extends_found = false; $this->_pf_includename_isset = false; $this->_pf_get_source = false; $this->_pf_getting_source = false; $this->_pf_in_class = false; $this->_pf_in_define = false; $this->_pf_in_global = false; $this->_pf_in_include = false; $this->_pf_in_var = false; $this->_pf_funcparam_val = false; $this->_pf_quote_active = false; $this->_pf_reset_quote_data = true; $this->_pf_useperiod = false; $this->_pf_var_equals = false; $this->_pf_obj_op = false; $this->_pf_docblock = false; $this->_pf_docblock_template = false; $this->_pf_colon_colon = false; $this->_pv_last_string = false; $this->_pf_inmethod = false; $this->_pf_no_output_yet = false; $this->_pv_saveline = 0; $this->_pv_next_word = false; $this->_save = ''; } /** * Initialize the {@link $tokenpushEvent, $wordpushEvent} arrays * * @return void */ function phpDocumentor_HighlightParser() { if (!defined('T_INTERFACE')) { define('T_INTERFACE', -1); } $this->allowableTags = $GLOBALS['_phpDocumentor_tags_allowed']; $this->allowableInlineTags = $GLOBALS['_phpDocumentor_inline_doc_tags_allowed']; $this->inlineTagHandlers = array('*' => 'handleDefaultInlineTag'); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_NOEVENTS] = array( T_OPEN_TAG => PARSER_EVENT_PHPCODE, ); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_PHPCODE] = array( T_FUNCTION => PARSER_EVENT_FUNCTION, T_CLASS => PARSER_EVENT_CLASS, T_INTERFACE => PARSER_EVENT_CLASS, T_INCLUDE_ONCE => PARSER_EVENT_INCLUDE, T_INCLUDE => PARSER_EVENT_INCLUDE, T_START_HEREDOC => PARSER_EVENT_EOFQUOTE, T_REQUIRE => PARSER_EVENT_INCLUDE, T_REQUIRE_ONCE => PARSER_EVENT_INCLUDE, T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpushEvent[PARSER_EVENT_PHPCODE] = array( "define" => PARSER_EVENT_DEFINE, '"' => PARSER_EVENT_QUOTE, '\'' => PARSER_EVENT_QUOTE, ); /**************************************************************/ $this->wordpushEvent[PARSER_EVENT_FUNCTION] = array( '{' => PARSER_EVENT_LOGICBLOCK, '(' => PARSER_EVENT_FUNCTION_PARAMS, ); $this->tokenpushEvent[PARSER_EVENT_FUNCTION] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpopEvent[PARSER_EVENT_FUNCTION] = array("}"); /**************************************************************/ $this->tokenpopEvent[PARSER_EVENT_EOFQUOTE] = array(T_END_HEREDOC); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_FUNCTION_PARAMS] = array( T_CONSTANT_ENCAPSED_STRING => PARSER_EVENT_QUOTE, T_ARRAY => PARSER_EVENT_ARRAY, T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpushEvent[PARSER_EVENT_FUNCTION_PARAMS] = array( '"' => PARSER_EVENT_QUOTE, "'" => PARSER_EVENT_QUOTE, ); $this->wordpopEvent[PARSER_EVENT_FUNCTION_PARAMS] = array(")"); /**************************************************************/ $this->wordpushEvent[PARSER_EVENT_LOGICBLOCK] = array( "{" => PARSER_EVENT_LOGICBLOCK, '"' => PARSER_EVENT_QUOTE, ); $this->tokenpushEvent[PARSER_EVENT_LOGICBLOCK] = array( T_GLOBAL => PARSER_EVENT_FUNC_GLOBAL, T_STATIC => PARSER_EVENT_STATIC_VAR, T_START_HEREDOC => PARSER_EVENT_EOFQUOTE, T_CURLY_OPEN => PARSER_EVENT_LOGICBLOCK, T_DOLLAR_OPEN_CURLY_BRACES => PARSER_EVENT_LOGICBLOCK, ); $this->wordpopEvent[PARSER_EVENT_LOGICBLOCK] = array("}"); $this->tokenpopEvent[PARSER_EVENT_LOGICBLOCK] = array(T_CURLY_OPEN); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_ARRAY] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpopEvent[PARSER_EVENT_ARRAY] = array(")"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_FUNC_GLOBAL] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpopEvent[PARSER_EVENT_FUNC_GLOBAL] = array(";"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_STATIC_VAR] = array( T_CONSTANT_ENCAPSED_STRING => PARSER_EVENT_QUOTE, T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpushEvent[PARSER_EVENT_STATIC_VAR] = array( "=" => PARSER_EVENT_STATIC_VAR_VALUE, ); $this->wordpopEvent[PARSER_EVENT_STATIC_VAR] = array(";"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_STATIC_VAR_VALUE] = array( T_CONSTANT_ENCAPSED_STRING => PARSER_EVENT_QUOTE, T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, T_ARRAY => PARSER_EVENT_ARRAY, ); $this->wordpushEvent[PARSER_EVENT_STATIC_VAR_VALUE] = array( '"' => PARSER_EVENT_QUOTE, "'" => PARSER_EVENT_QUOTE, ); $this->wordpopEvent[PARSER_EVENT_STATIC_VAR_VALUE] = array(";", ","); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_QUOTE] = array( T_OBJECT_OPERATOR => PARSER_EVENT_CLASS_MEMBER, T_CURLY_OPEN => PARSER_EVENT_QUOTE_VAR, ); $this->wordpopEvent[PARSER_EVENT_QUOTE] = array('"'); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_QUOTE_VAR] = array( T_OBJECT_OPERATOR => PARSER_EVENT_CLASS_MEMBER, T_CURLY_OPEN => PARSER_EVENT_QUOTE_VAR, ); $this->wordpushEvent[PARSER_EVENT_QUOTE_VAR] = array( "{" => PARSER_EVENT_QUOTE_VAR, '"' => PARSER_EVENT_QUOTE_VAR, "'" => PARSER_EVENT_QUOTE_VAR, ); $this->wordpopEvent[PARSER_EVENT_QUOTE_VAR] = array('}'); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_DEFINE] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, T_CONSTANT_ENCAPSED_STRING => PARSER_EVENT_QUOTE, ); $this->wordpushEvent[PARSER_EVENT_DEFINE] = array( "(" => PARSER_EVENT_DEFINE_PARAMS, ); $this->wordpopEvent[PARSER_EVENT_DEFINE] = array(";"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_DEFINE_PARAMS] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpushEvent[PARSER_EVENT_DEFINE_PARAMS] = array( "(" => PARSER_EVENT_DEFINE_PARAMS_PARENTHESIS, '"' => PARSER_EVENT_QUOTE, "'" => PARSER_EVENT_QUOTE, ); $this->wordpopEvent[PARSER_EVENT_DEFINE_PARAMS] = array(")"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_DEFINE_PARAMS_PARENTHESIS] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpushEvent[PARSER_EVENT_DEFINE_PARAMS_PARENTHESIS] = array( "(" => PARSER_EVENT_DEFINE_PARAMS_PARENTHESIS, '"' => PARSER_EVENT_QUOTE, "'" => PARSER_EVENT_QUOTE, ); $this->wordpopEvent[PARSER_EVENT_DEFINE_PARAMS_PARENTHESIS] = array(")"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_VAR] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, T_ARRAY => PARSER_EVENT_ARRAY, ); $this->wordpopEvent[PARSER_EVENT_VAR] = array(";"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_CLASS] = array( T_FUNCTION => PARSER_EVENT_METHOD, T_VAR => PARSER_EVENT_VAR, T_COMMENT => PARSER_EVENT_DOCBLOCK, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, T_CLOSE_TAG => PARSER_EVENT_OUTPHP, ); $this->wordpopEvent[PARSER_EVENT_CLASS] = array("}"); /**************************************************************/ $this->wordpushEvent[PARSER_EVENT_METHOD] = array( '{' => PARSER_EVENT_METHOD_LOGICBLOCK, '(' => PARSER_EVENT_FUNCTION_PARAMS, ); $this->tokenpushEvent[PARSER_EVENT_METHOD] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpopEvent[PARSER_EVENT_METHOD] = array("}", ";"); /**************************************************************/ $this->wordpushEvent[PARSER_EVENT_METHOD_LOGICBLOCK] = array( "{" => PARSER_EVENT_METHOD_LOGICBLOCK, '"' => PARSER_EVENT_QUOTE, ); $this->tokenpushEvent[PARSER_EVENT_METHOD_LOGICBLOCK] = array( T_OBJECT_OPERATOR => PARSER_EVENT_CLASS_MEMBER, T_GLOBAL => PARSER_EVENT_FUNC_GLOBAL, T_STATIC => PARSER_EVENT_STATIC_VAR, T_CURLY_OPEN => PARSER_EVENT_LOGICBLOCK, T_DOLLAR_OPEN_CURLY_BRACES => PARSER_EVENT_LOGICBLOCK, ); $this->wordpopEvent[PARSER_EVENT_METHOD_LOGICBLOCK] = array("}"); $this->tokenpopEvent[PARSER_EVENT_METHOD_LOGICBLOCK] = array(T_CURLY_OPEN); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_INCLUDE] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpushEvent[PARSER_EVENT_INCLUDE] = array( "(" => PARSER_EVENT_INCLUDE_PARAMS, ); $this->wordpopEvent[PARSER_EVENT_INCLUDE] = array(";"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_INCLUDE_PARAMS] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpushEvent[PARSER_EVENT_INCLUDE_PARAMS] = array( "(" => PARSER_EVENT_INCLUDE_PARAMS_PARENTHESIS, ); $this->wordpopEvent[PARSER_EVENT_INCLUDE_PARAMS] = array(")"); /**************************************************************/ $this->tokenpushEvent[PARSER_EVENT_INCLUDE_PARAMS_PARENTHESIS] = array( T_COMMENT => PARSER_EVENT_COMMENT, T_DOC_COMMENT => PARSER_EVENT_DOCBLOCK, ); $this->wordpushEvent[PARSER_EVENT_INCLUDE_PARAMS_PARENTHESIS] = array( "(" => PARSER_EVENT_INCLUDE_PARAMS_PARENTHESIS, ); $this->wordpopEvent[PARSER_EVENT_INCLUDE_PARAMS_PARENTHESIS] = array(")"); } } ?>