Source for file HighlightParser.inc
Documentation is available at HighlightParser.inc
* Source Code Highlighting
* The classes in this file are responsible for the dynamic @example, @filesource
* and {@}source} tags output. Using the phpDocumentor_HighlightWordParser,
* the phpDocumentor_HighlightParser retrieves PHP tokens one by one from the
* array generated by {@link phpDocumentorTWordParser} 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
* Copyright (c) 2002-2008 Gregory Beaver
* 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
* 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
* @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 $
* @link http://www.phpdoc.org
* @link http://pear.php.net/PhpDocumentor
* @tutorial tags.example.pkg, tags.filesource.pkg, tags.inlinesource.pkg
* @todo CS cleanup - change package to PhpDocumentor
* Retrieve tokens from an array of tokens organized by line numbers
* @category ToolsAndUtilities
* @copyright 2002-2008 Gregory Beaver
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @version Release: @VER@
* @link http://www.phpdoc.org
* @link http://pear.php.net/PhpDocumentor
* @todo CS cleanup - change package to PhpDocumentor
* @todo CS cleanup - change class name to PhpDocumentor_*
* Hash used to keep track of line numbers that have already been initialized
var $_listLineNums =
array();
* Initialize the parser object
* @param array &$input the input
* @param phpDocumentor_HighlightParser &$parser the parser
function setup(&$input, &$parser)
$this->_parser =
&$parser;
$linenum =
$this->linenum;
if (!isset
($this->_all[$this->linenum][$this->pos])) {
$token =
$this->_all[$linenum][$pos];
debug('Next Token ' .
$this->linenum .
'-' .
$this->pos .
':' .
$details);
* Retrieve the position of the next token that will be parsed
* in the internal token array
* @return array format: array(line number, position)
$linenum =
$this->linenum;
if (!isset
($this->_all[$this->linenum][$this->pos])) {
if (!isset
($this->_all[$linenum][$pos])) {
return array($linenum, $pos);
* Retrieve the next token
* @return array|stringeither array(PHP token constant, token) or string
if (!isset
($this->_all[$this->linenum][$this->pos])) {
if (!isset
($this->_all[$this->linenum])) {
$this->_parser->newLineNum();
$word =
$this->_all[$this->linenum][$this->pos++
];
* 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
function backupPos($last_token, $is_pos =
false)
$this->linenum =
$last_token[0];
$this->pos =
$last_token[1];
if ($last_token ===
false) {
//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]);
if ($this->linenum <
0) {
$this->pos =
count($this->_all[$this->linenum]) -
1;
$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
* @copyright 2002-2008 Gregory Beaver
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @version Release: @VER@
* @link http://www.phpdoc.org
* @link http://pear.php.net/PhpDocumentor
* @todo CS cleanup - change package to PhpDocumentor
* @todo CS cleanup - change class name to PhpDocumentor_*
* Highlighted source is built up in this string
* contents of the current source code line as it is parsed
* Used to retrieve highlighted tokens
* @var Converter a descendant of Converter
* Path to file being highlighted, if this is from a @filesource tag
* @var false|stringfull path
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
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
* @uses Converter::SourceLine() encloses {@link $_line} in a
* converter-specific format
if ($this->_pf_no_output_yet) {
$this->_line .=
$this->_converter->flushHighlightCache();
$this->_output .=
$this->_converter->SourceLine($this->_wp->linenum,
$this->_line, $this->_path);
* Start the parsing at a certain line number
* @param int $num line number
$this->_wp->linenum =
$num;
* 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
* @param array &$parse_data the parse data
* @param Converter &$converter the converter object
* @param bool $inlinesourceparse whether this data is from an
* @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
* @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
* @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)
$parse_data =
join($parse_data, '');
$parse_data =
explode("\n", $parse_data);
foreach ($parse_data as $linenum =>
$line) {
$this->_output .=
$converter->SourceLine($linenum,
return $converter->PreserveWhiteSpace($this->_output);
$this->_converter =
&$converter;
$converter->startHighlight();
$this->_path =
$filesourcepath;
if ($linenum !==
false) {
// initialize variables so E_ALL error_reporting doesn't complain
$pevent =
$this->_event_stack->getEvent();
if ($lpevent !=
$pevent) {
$this->_last_pevent =
$lpevent;
$this->_wp->setWhitespace(true);
$this->_wp->setWhitespace(false);
if (is_array($word) &&
$word[0] !=
T_WHITESPACE) {
$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) &&
//debug("added " . $this->_wp->linenum . '-' . $this->_wp->pos);
$this->_addoutput($word);
$this->_pv_last_word =
$lw;
$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,
if (PHPDOCUMENTOR_DEBUG ==
true) {
echo
"|" .
$this->_pv_last_word;
//echo "LINE: " . $this->_line . "\n";
//echo "OUTPUT: " . $this->_output . "\n";
echo
$dbg_linenum .
'-' .
$dbg_pos .
": ";
$this->_wp->printState();
$tok1 =
$this->_pv_next_word;
$tok =
$this->_wp->_all[$tok1[0]][$tok1[1]];
echo
token_name($tok[0]) .
' => ' .
$tok1[0] .
'-' .
$tok1[1] .
echo
"-------------------\n\n\n";
$this->$handle($word, $pevent);
} elseif ($word !==
false) {
debug('WARNING: possible error, no handler for event number '
die("FATAL ERROR, recursion limit reached");
} while (!($word ===
false));
* 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}
* Most tokens only need highlighting, and this method handles them
function defaultHandler($word, $pevent)
$this->_addoutput($word);
* Handles global declarations in a function, like:
* global $_phpDocumentor_setting;
* @uses _globallink() instead of _addoutput(), to link to global variables
* if they are used in a function
function handleFuncGlobal($word, $pevent)
$this->_globallink($word);
* Handles strings in quotation marks and heredoc
* Special handling is needed for strings that contain variables like:
* <code>$a = "$test string"</code>
* 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;
$this->_addoutput($word);
if ($this->_pf_quote_active &&
(($this->_pv_last_word ==
'"' &&
$this->_pv_last_word[0] ==
T_END_HEREDOC &&
$this->_pf_quote_active =
false;
$this->_wp->backupPos($word);
$this->_event_stack->popEvent();
if (!$this->_pf_quote_active &&
(($this->_pv_last_word ==
'"' &&
$this->_pv_last_word[0] ==
T_END_HEREDOC &&
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);
} 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);
$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)
$this->_pf_quote_active =
true;
$this->_addoutput($word);
if ($this->_pf_inmethod &&
is_array($word) &&
$word[0] ==
T_VARIABLE) {
$this->_pv_lastvar =
$word;
$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)
if (!isset
($token_save)) {
if (!isset
($this->_pv_define_params_data)) {
$this->_pv_define_params_data =
'';
$this->_addoutput($word);
if ($this->_pf_definename_isset) {
$this->_addoutput($word);
$this->_pv_define_params_data .=
$word;
if (substr($this->_pv_define_params_data, 0, 1) ==
substr($this->_pv_define_params_data,
strlen($this->_pv_define_params_data) -
1) &&
// remove leading and ending quotation marks
$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) {
$this->_addoutput($this->_converter->returnSee($link,
$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)
if (substr($word[1], 0, 2) ==
'/*' &&
strpos($word[1], '*/')) {
$this->_pv_last_word =
$word;
if ($word[1] ==
'/**#@-*/') {
$this->_pf_docblock_template =
true;
$this->_pf_docblock =
true;
$this->_pf_no_output_yet =
true;
$this->_pv_saveline =
$this->_wp->linenum +
1;
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;
* 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)
$this->_addoutput($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)
$this->_addoutput($word);
$this->_addoutput($word);
$this->_methodlink($word);
* Handler for the stuff between ( and ) in a function declaration
* function handles($only,$these,$parameters){...}
function handleFunctionParams($word, $pevent)
$this->_addoutput($word);
$this->_addoutput($word);
* 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
function handleLogicBlock($word, $pevent)
$this->_addoutput($word);
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;
$e =
$this->_event_stack->popEvent();
$this->_event_stack->pushEvent($e);
$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');
unset
($this->_pv_prev_var_type);
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;
$this->_addoutput($word);
$this->_pf_no_output_yet =
true;
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->_pf_inmethod =
false;
$e =
$this->_event_stack->popEvent();
$this->_event_stack->pushEvent($e);
$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->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
* 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
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
$save =
$this->_wp->nextToken();
$temp =
$this->_wp->getWord();
$this->_wp->backupPos($save, true);
if ((is_string($word) &&
$word ==
'(') ||
(isset
($temp) &&
$this->_pf_no_output_yet =
false;
$this->_methodlink($name);
unset
($this->_pv_prev_var_type);
//fancy_debug('name is ', $name);
$this->_pf_no_output_yet =
false;
$this->_varlink($name, true);
$this->_converter->getLink('object ' .
$this->_pv_class);
$class =
$this->_converter->classes
->getClass($templink->name, $templink->path);
if ($varname{0} !=
'$') {
$var =
$class->getVar($this->_converter, $varname);
if (is_object($var) &&
$var->docblock->var) {
$type =
$var->docblock->var->returnType;
if (strpos($type, 'object') ===
false) {
$type =
$this->_converter->getLink($type);
// the variable's type is a class,
//fancy_debug('set prev_var_type!', $type->name);
$this->_pv_prev_var_type =
$type->name;
unset
($this->_pv_prev_var_type);
unset
($this->_pv_prev_var_type);
unset
($this->_pv_prev_var_type);
$this->_pf_no_output_yet =
false;
// this does NewLinenum if necessary
$this->_wp->backupPos($word);
$this->_addoutput($name);
//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);
if ($word[0] ==
T_OBJECT_OPERATOR) {
$this->_wp->backupPos($word);
$this->_addoutput($word);
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;
$this->_pf_no_output_yet =
false;
// this does NewLinenum if necessary
$this->_wp->backupPos($word);
$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->_addoutput($word);
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->_addoutput($word);
if ($word[0] ==
T_STRING) {
//fancy_debug('set pvcmname to', $word);
$this->_pv_cm_name =
$word;
$this->_pf_no_output_yet =
false;
// this does NewLinenum if necessary
$this->_wp->backupPos($word);
$this->_addoutput($word);
* 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) &&
$this->_event_stack->popEvent();
if ($this->_pf_no_output_yet) {
$this->_pf_no_output_yet =
false;
$this->_addoutput($this->_pv_last_word);
strpos($word[1], '/**') ===
0)
$this->_event_stack->popEvent();
if (strpos($this->_pv_last_word[1], "\n") !==
false) {
$this->_wp->backupPos($this->_pv_last_word);
//var_dump($this->_wp->nextToken());
} elseif (isset
($flag)) {
$this->_addoutput($word);
$this->_event_stack->popEvent();
* Handle class declarations
* Handles the initial declaration line:
* <code>class X extends Y implements I</code>
* @uses _classlink() to link to documentation for X and for Y class in
function handleClass($word, $pevent)
$this->_pf_in_class =
true;
if (!isset
($this->_pv_class) &&
is_array($word) &&
$word[0] ==
T_STRING) {
$this->_pv_class =
$this->_converter->class =
$word[1];
$this->_classlink($word);
$starttok =
$this->_wp->nextToken();
$test =
array(T_WHITESPACE);
while ($test &&
$test[0] ==
T_WHITESPACE) {
$tok =
$this->_wp->nextToken();
$test =
$this->_wp->getWord();
if (is_array($test) &&
$test[0] ==
T_VARIABLE) {
$this->_wp->backupPos($tok, true);
$this->_wp->backupPos($starttok, true);
if (is_array($word) &&
$word[0] ==
T_VARIABLE) {
$this->_wp->backupPos($this->_pv_last_word);
if ($this->_pf_extends_found &&
is_array($word) &&
$word[0] ==
T_STRING) {
$this->_classlink($word);
if (is_array($word) &&
$word[0] ==
T_EXTENDS) {
$this->_pf_extends_found =
true;
$this->_pf_no_output_yet =
true;
$this->_pv_saveline =
$this->_wp->linenum +
1;
$this->_addoutput($word);
$this->_pf_in_class =
false;
* Handles class variable declaration
* @uses _varlink() make a link to $Y documentation in class variable
function handleVar($word, $pevent)
$this->_addoutput($word);
if (is_array($word) &&
$word[0] ==
T_VARIABLE) {
return $this->_varlink($word);
$this->_addoutput($word);
* 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
* 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) {
$this->_wp->backupPos($this->_pv_last_word);
$this->_event_stack->popEvent();
$this->_pf_no_output_yet =
false;
$this->_pf_no_output_yet =
true;
$this->_pv_db_lines =
array();
$last_word =
$this->_pv_last_word[1];
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();
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;
$this->_pf_docblock =
true;
$this->_pv_db_lines[] =
$last_word;
if (strpos($last_word, '*/') !==
false) {
$this->_pv_db_lines[] =
$word[1];
if (strpos($word[1], '*/') !==
false) {
$this->_pv_db_lines[] =
$word[1];
if (($this->_pf_docblock ||
$this->_pf_docblock_template) &&
(strpos($word[1], '*/') !==
false)
* 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
* @uses handleDesc() highlight inline tags in the description
* @uses handleTags() highlight all tags
function commonDocBlock()
$this->_event_stack->popEvent();
$lines =
$this->_pv_db_lines;
$go =
count($this->_pv_db_lines);
for ($i=
0; $i <
$go; $i++
) {
$lines[$i] =
array($lines[$i], false);
} elseif (substr(trim($lines[$i]), 0, 3) ==
'/**') {
for ($i =
0; $i <
count($lines); $i++
) {
if ($lines[$i][1] ===
false) {
//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)
function handleDesc($desc)
$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)
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
function handleTags($tags)
for ($i=
0; $i <
count($tags); $i++
) {
$tagsi =
trim($tags[$i][1]);
if (substr($tagsi, 0, 1) ==
'@' &&
substr($tagsi, 0, 2) !=
'@ ') {
$tags[$i][1] =
array(substr($tags[$i][1], 0,
strpos($tags[$i][1], $tagsi)), $tagsi);
foreach ($newtags as $tag) {
foreach ($tag as $i =>
$t) {
if (isset
($this->tagHandlers[$tagname])) {
$handle =
$this->tagHandlers[$tagname];
$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
function getInlineTags($value, $endinternal =
false)
if ($this->_pf_internal &&
!$endinternal) {
if (strpos($value, '}}') !==
false) {
// add the rest of internal
$this->getInlineTags(substr($value, 0, $x +
3), true);
// strip internal from value
$this->_pf_internal =
false;
$dbtype .=
($this->_pf_docblock ?
'' :
'template');
// 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));
$value[$i] =
explode(" ", $value[$i]);
$val =
join(' ', $value[$i]);
if ($word ==
'internal') {
$this->_pf_internal =
true;
$this->_addDocBlockoutput($dbtype, '{@internal ');
// strip internal and cycle as if it were normal text.
$this->_addDocBlockoutput($dbtype, $value[$i]);
//addError(PDERROR_UNTERMINATED_INLINE_TAG,
$rest =
join('}', $rest);
$rest =
join(' ', $rest);
$this->$handle($word, $val);
$this->_addDocBlockoutput($dbtype, $rest);
$val =
$word .
' ' .
$val;
$this->_addDocBlockoutput($dbtype, '{@' .
$val);
* Handles all inline tags
* @param string $name the tag name
* @param mixed $value the tag value
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
* 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
$dbtype .=
($this->_pf_docblock ?
'' :
'template');
foreach ($value as $line) {
$this->_addDocBlockoutput($dbtype, $line[0]);
if ($line[1] ===
false) {
if (trim($line[0]) !=
'*/') {
$this->_addDocBlockoutput($dbtype, $line[1][0]);
foreach ($line[1][1] as $i =>
$tpart) {
if ($tpart ==
'@' .
$name &&
$i ==
0) {
$this->_addDocBlockoutput($tagname, '@' .
$name);
$this->getInlineTags($stored);
if (strpos($stored, '*/') ===
false) {
* main handler for "core" tags
* @see defaultTagHandler()
function coreTagHandler($name, $value)
* This handler works like {@link defaultTagHandler()} except it highlights
* the type and variable (if present) in "@global type $variable" or
* "@global type description"
* 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
$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->_addDocBlockoutput($dbtype, $line[1][0]);
$this->_addDocBlockoutput('coretag', '@' .
$name .
' ');
foreach ($ret[0] as $text) {
$this->_addDocBlockoutput($dbtype, $text);
if ($text[0] !=
'desc') {
$this->_addDocBlockoutput($text[0], $text[1]);
foreach ($ret[$num] as $text) {
$this->_addDocBlockoutput($dbtype, $text);
if ($text[0] !=
'desc') {
$this->_addDocBlockoutput($text[0], $text[1]);
$this->getInlineTags($stored);
if (strpos($stored, '*/') ===
false) {
* handles the @staticvar tag
* This handler works like {@link defaultTagHandler()} except it highlights
* the type in "@return type description"
* Handles @property(-read or -write) and @method magic tags
* 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]),
function retrieveType($value, $state =
0, $checkforvar =
false)
if (!isset
($value[$index][1])) {
$val =
$value[$index][1];
$ret =
$this->_retrieveType($val, $state, $checkforvar);
$result[$index++
] =
$ret[1];
} while ((!$checkforvar &&
$state <
1) ||
($state <
2 &&
$checkforvar));
* 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
function _retrieveType($value, $state, $checkforvar)
$result[] =
$this->_removeWhiteSpace($value, 0);
return array(2, $result);
if (trim($value[0]) ==
'object') {
$result[] =
array('tagphptype', $value[0] .
' ');
$result[] =
$this->_removeWhiteSpace($value, 0);
// 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
$result[] =
array('tagvarname' , $value[0] .
' ');
$result[] =
array('desc', join(' ', $value));
return array(2, $result);
// this loop checks for type|type|type and for
// type|object classname|type|object classname2
$temptypes =
explode('|', $value[0]);
while (count($temptypes)) {
$result[] =
array('tagphptype', $type);
if (trim($type) ==
'object') {
$result[] =
array('tagphptype', $types .
' ');
$result[] =
$this->_removeWhiteSpace($value, 0);
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] .
' ');
$result[] =
array('desc', join(' ', $value));
return array(2, $result);
$result[] =
array('tagphptype', $value[0] .
' ');
} while (!$done &&
count($value));
// 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);
if (substr($value[0], 0, 1) ==
'$' ||
substr($value[0], 0, 2) ==
'&$'
$result[] =
array('tagvarname' , $value[0] .
' ');
$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
function _removeWhiteSpace(&$value, $index)
if (count($value) >
$index &&
empty($value[$index])) {
for ($i =
$index; $i <
count($value) &&
!strlen($value[$i]); $i++
) {
* Link generation methods
* @param string|array $word token to try to link
* 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
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);
$this->_addoutput($this->_converter->returnSee($link,
$this->_addoutput($word);
$link =
$this->_converter->getLink($word[1] .
'()');
$this->_addoutput($this->_converter->returnSee($link,
$this->_addoutput($this->_converter->returnLink($link,
$link =
$this->_converter->getLink($word[1]);
$word[1] =
$this->_converter->returnSee($link, $word[1]);
$this->_addoutput($word, true);
$this->_addoutput($word);
* Works like {@link _link()} except it only links to global variables
function _globallink($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]);
$this->_addoutput($this->_converter->returnSee($link,
$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]);
$this->_addoutput($this->_converter->returnSee($link,
$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 .
'::' .
$link =
$this->_converter->getLink($this->_pv_class .
'::' .
$this->_addoutput($this->_converter->returnSee($link,
if (isset
($this->_pv_prev_var_type)) {
$this->_addoutput($word);
//debug("checking method " . $word[1] . '()');
$link =
$this->_converter->getLink($word[1] .
'()');
$this->_addoutput($this->_converter->returnSee($link,
$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 (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 . '::' .
$link =
$this->_converter->getLink($this->_pv_prev_var_type .
'::' .
$link =
$this->_converter->getLink($this->_pv_class .
'::' .
$this->_addoutput($this->_converter->returnSee($link,
//debug("checking var " . $x . $word[1]);
if (isset
($this->_pv_prev_var_type)) {
$this->_addoutput($word);
$link =
$this->_converter->getLink($x .
$word[1]);
$this->_addoutput($this->_converter->returnSee($link,
$this->_addoutput($word);
* 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
function _addoutput($word, $preformatted =
false)
if ($this->_pf_no_output_yet) {
if ($this->_pf_quote_active) {
$this->_save .=
$this->_converter->highlightSource($word[0],
$this->_save .=
$this->_converter->highlightSource(false,
$this->_line .=
$this->_converter->postProcess($word);
$this->_line .=
$this->_converter->postProcess($word[1]);
$this->_line .=
$this->_converter->highlightSource($word[0],
$word[1], $preformatted);
$this->_line .=
$this->_converter->highlightSource(false,
* 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
function _addDocBlockoutput($dbtype, $word, $preformatted =
false)
if ($this->_pf_internal) {
$this->_line .=
$this->_converter->highlightDocBlockSource('internal',
$this->_line .=
$this->_converter->highlightDocBlockSource($dbtype,
* Flush a saved string variable highlighting
* @todo CS cleanup - rename to _flushSave() for camelCase rule
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)) {
_setHighlightCache($this->_save_highlight_state[0],
$this->_save_highlight_state[1]);
$this->_line .=
$this->_converter->
highlightSource(T_CONSTANT_ENCAPSED_STRING, $this->_save, true);
* Give the word parser necessary data to begin a new parse
* @param array &$data all tokens separated by line number
$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
* @uses $_wp sets to a new {@link phpDocumentor_HighlightWordParser}
if ($inlinesourceparse) {
$this->_pv_class =
$class;
$this->_pv_define =
null;
$this->_pv_define_name =
null;
$this->_pv_define_value =
null;
$this->_pv_define_params_data =
null;
$this->_pv_docblock =
null;
$this->_pv_dtemplate =
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_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_next_word =
false;
* Initialize the {@link $tokenpushEvent, $wordpushEvent} arrays
=
$GLOBALS['_phpDocumentor_tags_allowed'];
$this->allowableInlineTags
=
$GLOBALS['_phpDocumentor_inline_doc_tags_allowed'];
=
array('*' =>
'handleDefaultInlineTag');
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/
/**************************************************************/