* @copyright  2002-2006 Gregory Beaver
 * @license    http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @version    CVS: $Id: IntermediateParser.inc 247821 2007-12-09 06:11:35Z ashnazg $
 * @filesource
 * @link       http://www.phpdoc.org
 * @link       http://pear.php.net/PhpDocumentor
 * @since      1.1
 */
/** The phpDocumentor_IntermediateParser Class
 *
 * This class performs the work of organizing raw data from the parser in the
 * format of descendants of the {@link parserElement} class.  This is also where
 * processing of package pages occurs, in
 * {@link phpDocumentor_IntermediateParser::handleClass()} for class-level
 * packages and {@link phpDocumentor_IntermediateParser::handleDocBlock()} for
 * page-level packages.
 *
 * Most of the work of this parser goes to matching up
 * DocBlocks with the elements that they are documenting.  Since DocBlocks are
 * passed before the element they document, the last DocBlock is stored in
 * {@link phpDocumentor_IntermediateParser::$last} and then placed into the
 * $docblock parameter of the parserElement
 * descendant object.
 *  @author Gregory Beaver
 *  @version $Id: IntermediateParser.inc 247821 2007-12-09 06:11:35Z ashnazg $
 *  @copyright 2002 Gregory Beaver
 *  @package     phpDocumentor
 */
class phpDocumentor_IntermediateParser
{
    /**
     * @var parserDocBlock
     */
    var $last;
    
    /**
     * type of the last parser Element handled
     *
     * This is used in handleDocBlock to determine whether a DocBlock is a
     * page-level DocBlock in conjunction with the {@link parserData::$clean}
     * var.  A page-level DocBlock is alwaysthe first DocBlock in a file, and
     * must be followed by another DocBlock.  The first test is handled by
     * parserData::$clean, which is set to false on the first encounter of an
     * element, and the second test is handled by this variable, which must be
     * equal to "docblock"
     * @see handleDocBlock()
     * @var string
     */
    var $lasttype = '';
    
    /**
     * Name of the class currently being parsed.
     * It is only used (and only valid) when phpDocumentor_IntermediateParser is
     * parsing a class
     * @var string
     */
    var $cur_class = '';
    
    /**
     * type of the current parser Element being handled
     * 
     * This is used by {@link HandleEvent()} to set the {@link $lasttype} var,
     * which is used to detect page-level DocBlocks
     * @var string
     */
    var $type = '';
    
    /**
     * set in {@link Setup.inc.php} to the value of the parseprivate commandline
     * option.  If this option is true, elements with an @access private tag
     * will be parsed and displayed
     * @tutorial phpDocumentor.howto.pkg#using.command-line.parseprivate
     * @var boolean
     */
    var $parsePrivate = false;
    
    /**
     * this variable is used to prevent parsing of elements with an @ignore tag
     * @see $packageoutput
     * @see $parsePrivate
     */
    var $private_class = false;
    
    /**
     * used to set the output directory
     * @see setTargetDir()
     */
    var $targetDir;
    
    /**
     * used to set the template base directory
     * @see setTemplateBase()
     */
    var $templateBase;
    
    /**
     * array of parsed package pages
     *
     * used by {@link Convert()} to convert all package pages into output
     * @var array
     */
    var $package_pages = array();
    
    /**
     * @var array array of all {@link parserData} containing page information
     */
    var $pages = array();
    /**
     * Put away a page that has been @ignored or @access private if
     * !{@link $parsePrivate}
     *
     * When a page has @access private in its DocBlock, it is placed here
     * instead of in {@link $pages}, to allow for proper Class parsing.  Since
     * classes and pages are parsed as if they were separate, this array allows
     * public classes on private pages to retrieve information needed about the
     * page that holds the class and to {@link addPageIfNecessary()} to the
     * $pages array
     * @var array
     */
    var $privatepages = array();
    /**
     * Keeps track of packages of classes that have parent classes in another
     * package.  Used in automatic linking.
     *
     * This array is updated by {@link addPackageParent()}, which is called in
     * {@link Classes::processChild()} to keep track of classes that descend
     * from classes in different packages.  In other words, if class foo is in
     * package one, and class bar is in package two, an entry
     * $package_parents['two'] = 'one' will be made.
     * @var array Format: packagename => parentpackagename
     * @see Converter::getLink()
     */
    var $package_parents = array();
    
    /**
     * Used to determine the category for tutorials.
     *
     * WARNING: If more than one category exists, the last category
     * encountered will overwrite the previous and will raise a big warning
     * @var array Format: packagename => categoryname
     */
    var $packagecategories = array();
    
    /**
     * list of all packages encountered while documenting.  Used in automatic
     * linking.
     * 
     * Converter::getLink() first checks if an ambiguous link is found in the
     * current package.  If not, it then checks in parent packages, and if still
     * not found, uses this array to check in the rest of the packages before
     * giving up
     * @var array Format: array(packagename => 1, packagename => 1,...)
     * @see Converter::getLink()
     */
    var $all_packages = array();
    
    /**
     * array of packages to parser and output documentation for, if not all
     * packages should be documented
     *
     * Format:
     * array(package1,package2,...)
     * or false if not set
     *
     * Use this option to limit output similar to ignoring files.  If you have
     * some temporary files that you don't want to specify by name but don't
     * want included in output, set a package name for all the elements in your
     * project, and set packageoutput to that name.  the default package will be
     * ignored.  Parsing speed does not improve.  If you want to ignore files
     * for speed reasons, use the ignore command-line option
     * @tutorial phpDocumentor.howto.pkg#using.command-line.packageoutput
     * @see Io
     * @var false|array
     */
    var $packageoutput = false;
    
    /**
     * the functions which handle output from the {@link Parser}
     * @see handleEvent(), handleDocBlock(), handlePage(), handleClass()
     * @see handleDefine(), handleFunction(), handleMethod(), handleVar()
     * @see handlePackagePage(), handleInclude(), handleTutorial()
     */
    var $event_handlers = array(
            'docblock' => 'handleDocBlock',
            'page' => 'handlePage',
            'class' => 'handleClass',
            'define' => 'handleDefine',
            'function' => 'handleFunction',
            'method' => 'handleMethod',
            'var' => 'handleVar',
            'const' => 'handleConst',
            'packagepage' => 'handlePackagePage',
            'include' => 'handleInclude',
            'global' => 'handleGlobal',
            'tutorial' => 'handleTutorial',
            );
    
    /**
     * $data contains parsed structures for the current page being parsed
     *
     * In version 1.1+, $data is only used to store the current page information.
     * All handling of documented elements is handled by the
     * {@link ProceduralPages} and {@link Classes} classes.
     * @var parserData
     */
    var $data;
    
    /**
     * set in {@link Setup.inc.php} to the value of the quitemode commandline
     * option.
     *
     * If this option is true, informative output while parsing will not be
     * displayed (documentation is unaffected)
     * @var boolean
     * @tutorial phpDocumentor.howto.pkg#using.command-line.quiet
     */
    var $quietMode = false;
    /**
     * set in {@link Setup.inc.php} to the value of the undocumentedElementWarnings commandline
     * option.
     *
     * If this option is true, warnings about certain elements (classes, methods) 
     * that are not documented with DocBlocks will be shown while parsing,
     * and will also be displayed in the errors.html page 
     * (other documentation is unaffected)
     * @var boolean
     * @tutorial phpDocumentor.howto.pkg#using.command-line.undocumentedelements
     */
    var $undocumentedElementWarnings = false;
        
    /**
     * used to keep track of inheritance at the smartest level possible for a
     * dumb computer
     * @var Classes
     */
    var $classes = false;
    
    /**
     * used to keep track of all elements in a procedural page.  Handles name
     * conflicts with elegance
     * @since 1.1
     * @var ProceduralPages
     */
    var $proceduralpages = false;
    
    /**
     * an array of template names indexed by converter name
     *
     * For example, if the default HTMLframesConverter is using the DOM/l0l33t
     * template, the array will be
     * $converters['frames'] = 'DOM/l0l33t'
     * @var array Format: array(Convertername1 => templatename)
     * @see Converter
     */
    var $converters = false;
    /** 
     * @var string Title of generated documentation, passed to Converters
     */
    var $title = '';
    
    var $uses = array();
    var $db_template;
    
    /**
     * Stores parsed CHANGELOG/INSTALL/README files
     * @var array Format: array(CHANGELOG => contents,
     *                          INSTALL => contents,
     *                          README => contents)
     */
    var $ric = array();
    
    /**
     * Flag used to determine whether the last docblock
     * was a page-level docblock.
     * @var boolean
     * @access private
     */
    var $_lastDocBlockWasPageLevel = false;
    
    /**
     * Flag used to determine whether the Page-level
     * DocBlock was declared in old or new style
     * @var boolean
     * @access private
     */
    var $_oldPageLevel = false;
    /**
     * sets up basic data structures
     * @param string Title of generated documentation, passed to Converters
     * @see $title, $data, $classes, $proceduralpages
     */
    function phpDocumentor_IntermediateParser($title='Generated Documentation')
    {
        $this->title = $title;
        $this->data = new parserData;
        $this->classes = new Classes;
        $this->proceduralpages = new ProceduralPages;
    }
    
    /**
     * Retrieve the relative path.  If the path contains "pear/" it will
     * be used as the base, otherwise the Program_Root string will be used.
     * @global array uses 'Program_Root' option to replace it with '' for
     *               retrieving the source location of a file
     * @param string path to file
     * @return string
     * @see $sourceLocation
     * @access private
     */
    function _getSourceLocation($sl, $sourceloc)
    {
        global $_phpDocumentor_options;
        if (empty($sl)) return false;
        $sl = str_replace('\\','/',$sl);
        if (strpos($sl,'pear/'))
        {
            $sl = substr($sl,strpos($sl,'pear/') + 5);
            if (dirname($sl) == '.')
            {
                return 'PEAR';
            }
            return dirname($sl);
        } else
        {
            if (strpos(str_replace($_phpDocumentor_options['Program_Root'] . PATH_DELIMITER,'',$sourceloc),PATH_DELIMITER) === false)
                return '';
            return dirname(str_replace($_phpDocumentor_options['Program_Root'] . PATH_DELIMITER,'',$sourceloc));
        }
    }
    
    /**
     * Guess the package/subpackage based on subdirectory if the --pear option
     *
     * A file in pear/dir/file.php will be in package "dir."  A file in
     * pear/dir/subdir/file.php will be in package "dir," subpackage "subdir."
     * @param string full path of file
     * @param template-ready source location Program_Root/dir/file.php
     * @global array uses the 'pear' option to determine whether to guess based
     *               on subdirectory
     * @tutorial phpDocumentor.howto.pkg#using.command-line.pear
     */
    function _guessPackage($path, $sourceloc)
    {
        global $_phpDocumentor_setting;
        if ($_phpDocumentor_setting['pear'])
        {
            $subpath = explode(PATH_DELIMITER, $this->_getSourceLocation($path, $sourceloc));
            if (!empty($subpath[0]))
            { // can only have package and subpackage in this version
                $package = $subpath[0];
                $subpackage = '';
                if (isset($subpath[1])) $subpackage = $subpath[1];
                return array($package,$subpackage);
            } else return array($this->package, $this->subpackage);
        } else return array($this->package, $this->subpackage);
    }
    
    /**
     * handles post-parsing of include/require/include_once/require_once
     *
     * This function sets {@link $data}->clean to false to tell the
     * phpDocumentor_IntermediateParser that a page-level DocBlock can't be
     * found after this point on this page.  It then sets the package
     * to be the same as the page, and adds itself to the
     * {@link ProceduralPages} class
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserInclude $data
     */
    function handleInclude($event,$data)
    {
        if ($this->_lastDocBlockWasPageLevel)
        {
            addWarning(PDERROR_DOCBLOCK_CONFLICT, $data->getName(), $data->getValue());
            if (!$this->_oldPageLevel)
            {
                unset($this->last);
            }
        }
        $this->_lastDocBlockWasPageLevel = false;
        $this->data->clean = false;
        // page was @ignored
        if ($this->private_page)
        {
            unset($this->last);
            return;
        }
        if (empty($this->last))
        {
            if (isset($this->db_template))
            {
                // use the docblock template
                $this->last = phpDocumentor_clone($this->db_template);
            }
            else
            {
                // we don't have a docblock, create an empty one to get rid of errors
                $this->last = new parserDocblock();
            }
        }
//        $this->last->setLineNumber($data->getLineNumber());
        if ($this->last->getKeyword('ignore'))
        {
            $this->last = false;
            return;
//            addWarning(PDERROR_IGNORE_TAG_IGNORED,'include',$data->getName().'('.$data->getValue().')');
        }
        $this->last->overridePackage($this->category,$this->package,$this->subpackage,$data->getName(),'include');
        $data->setDocBlock($this->last);
        $this->proceduralpages->addInclude($data);
        $this->last = false;
    }
    
    /**
     * handles post-parsing of global variables
     *
     * This function sets {@link $data}->clean to false to tell the
     * phpDocumentor_IntermediateParser that a page-level DocBlock can't be
     * found after this point on this page.  It then sets the package
     * to be the same as the page, and adds itself to the
     * {@link ProceduralPages} class
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserGlobal $data
     */
    function handleGlobal($event,$data)
    {
        if ($this->_lastDocBlockWasPageLevel)
        {
            addWarning(PDERROR_DOCBLOCK_CONFLICT, 'global variable', $data->getName());
            if (!$this->_oldPageLevel)
            {
                unset($this->last);
            }
        }
        $this->_lastDocBlockWasPageLevel = false;
        $this->data->clean = false;
        if ($this->private_page)
        {
            unset($this->last);
            return;
        }
        if (empty($this->last))
        {
            if (isset($this->db_template))
            {
                // use the docblock template
                $this->last = phpDocumentor_clone($this->db_template);
            }
            else
            {
                // we don't have a docblock, create an empty one to get rid of errors
                $this->last = new parserDocblock();
            }
        }
//        $this->last->setLineNumber($data->getLineNumber());
        if ($this->last->getKeyword('ignore'))
        {
            addWarning(PDERROR_IGNORE_TAG_IGNORED,'global variable - just don\'t document the',$data->getName());
            $this->last = false;
            return;
        }
        $this->last->overridePackage($this->category,$this->package,$this->subpackage,$data->getName(),'global');
        $data->setDocBlock($this->last);
        if ($data->docblock->getKeyword('name'))
        {
            $a = $data->docblock->getKeyword('name');
            if (is_object($a)) $a = $a->value;
            $data->setName($a);
        }
        $this->proceduralpages->addGlobal($data);
        $this->last = false;
    }
    
    /**
     * handles post-parsing of Package-level documentation pages.
     *
     * sets the {@link $package_pages}[$data->package] to $data
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserPackagePage $data
     */
    function handlePackagePage($event,$data)
    {
        $this->package_pages[$data->package] = &$data;
        $this->last = false;
    }
    
    /**
     * handle post-parsing of Tutorials.
     *
     * This adds the parsed tutorial to the tutorial tree
     * @uses $tutorials sets the value of tutorials to parameter $data
     * @param integer $event Event Number
     * @param parserTutorial $data
     * @since 1.2
     */
    function handleTutorial($event,$data)
    {
        if (isset($this->packagecategories[$data->package]))
        {
            $data->category = $this->packagecategories[$data->package];
        } else
        {
            $data->category = $GLOBALS['phpDocumentor_DefaultCategoryName'];
        }
        $this->tutorials[$data->package][$data->subpackage][$data->tutorial_type][$data->name] = $data;
    }
    
    /**
     * handles post-parsing of class vars
     *
     * This function sets up a @var tag if none is found, and aligns $data's
     * $path var and packages to match the parent object
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserVar $data
     */
    function handleVar($event,$data)
    {
        global $_phpDocumentor_setting;
        if ($this->private_class)
        {
            unset($this->last);
            return;
        }
        if (empty($this->last))
        {
            if (isset($this->db_template))
            {
                // use the docblock template
                $this->last = phpDocumentor_clone($this->db_template);
            } 
            else 
            {
                // we don't have a docblock, create an empty one to get rid of errors
                $this->last = new parserDocblock();
            }
        }
//        $this->last->setLineNumber($data->getLineNumber());
        $this->last->overridePackage($this->category,$this->package,$this->subpackage,$data->getName(),'var');
        $this->last->updateModifiers($data->getModifiers());
        if ($this->last->getKeyword('ignore'))
        {
            $this->last = false;
            return;
//            addWarning(PDERROR_IGNORE_TAG_IGNORED,'var',$this->cur_class.'::'.$data->getName());
        }
        if (!$this->last->var)
        {
            $this->last->addVar('mixed',new parserStringWithInlineTags);
        }
        
        if ($_phpDocumentor_setting['pear'])
        {
            if (strpos($data->getName(), '_') == 1 && !$this->last->getKeyword('access'))
            {
                addWarning(PDERROR_PRIVATE_ASSUMED,'class variable',$data->class.'::'.$data->getName());
                $this->last->addKeyword('access','private');
                $data->setDocBlock($this->last);
            }
        }
        $data->setDocBlock($this->last);
        $data->path = $this->data->parent->path;
        $this->classes->addVar($data);
        $this->last = false;
    }
    
    /**
     * handles post-parsing of class constants
     *
     * This function aligns $data's
     * $path var and packages to match the parent object
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserVar $data
     */
    function handleConst($event,$data)
    {
        global $_phpDocumentor_setting;
        if ($this->private_class)
        {
            unset($this->last);
            return;
        }
        if (empty($this->last))
        {
            if (isset($this->db_template))
            {
                // use the docblock template
                $this->last = phpDocumentor_clone($this->db_template);
            }
            else
            {
                // we don't have a docblock, create an empty one to get rid of errors
                $this->last = new parserDocblock();
            }
        }
//        $this->last->setLineNumber($data->getLineNumber());
        $this->last->overridePackage($this->category,$this->package,$this->subpackage,$data->getName(),'const');
        if ($this->last->getKeyword('ignore'))
        {
            $this->last = false;
            return;
//            addWarning(PDERROR_IGNORE_TAG_IGNORED,'var',$this->cur_class.'::'.$data->getName());
        }
        $data->setDocBlock($this->last);
        $data->path = $this->data->parent->path;
        $this->classes->addConst($data);
        $this->last = false;
    }
    
    /**
     * handles post-parsing of class methods
     *
     * This function first aligns $data's path and package to match the parent
     * object, and also aligns the docblock's @param, @global, and @staticvar
     * tags with the information parsed from the method source code.  It also
     * checks to see if the method is a constructor and sets the $isConstructor
     * flag.  If source code has been parsed by a {@}source} tag, the source is
     * added to its docblock
     *
     * Finally, it adds the method to the {@link Classes} class.
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserMethod $data
     */
    function handleMethod($event,$data)
    {
        global $_phpDocumentor_setting;
        if ($this->private_class)
        {
            unset($this->last);
            return;
        }
        if (empty($this->last))
        {
			if ($this->undocumentedElementWarnings)
            {
                addWarning(PDERROR_UNDOCUMENTED_ELEMENT,'Method',$data->getName(),'method');
            }        	
            if (isset($this->db_template))
            {
                // use the docblock template
                $this->last = phpDocumentor_clone($this->db_template);
            }
            else
            {
                // we don't have a docblock, create an empty one to get rid of errors
                $this->last = new parserDocblock();
            }
        }
//        $this->last->setLineNumber($data->getLineNumber());
        if ($this->last->getKeyword('ignore'))
        {
            $this->last = false;
            return;
//            addWarning(PDERROR_IGNORE_TAG_IGNORED,'method',$this->cur_class.'::'.$data->getName());
        }
        $this->last->overridePackage($this->category,$this->package,$this->subpackage,$data->getName(),'method');
        if ($data->hasSource())
        {
            $this->last->setSource($data->getSource(), $data->getClass());
        }
        foreach($data->listParams() as $key => $param)
        {
            $update_params[$key] = $param;
        }
        foreach($data->listGlobals() as $param)
        {
            $update_globals[] = $param[1];
        }
        foreach($data->listStatics() as $param)
        {
            $update_statics[] = $param[0];
        }
        if (isset($update_params))
        $this->last->updateParams($update_params);
        if (isset($update_globals))
        $this->last->updateGlobals($update_globals);
        if (isset($update_statics))
        $this->last->updateStatics($update_statics);
        $this->last->updateModifiers($data->getModifiers());
        unset($update_params);
        unset($update_globals);
        unset($update_statics);
        if ($data->getName() == $this->cur_class) $data->setConstructor();
        if ($data->getName() == '__construct') {
            $data->setConstructor();
        }
        if ($data->getName() == '__destruct') {
            $data->setDestructor();
        }
        if ($_phpDocumentor_setting['pear'])
        {
            if (strpos($data->getName(), '_') === 0 && substr($data->getName(), 1) == $data->class)
            { // is destructor
                $data->setDestructor();
            } elseif (strpos($data->getName(), '_') === 0 && !$this->last->getKeyword('access'))
            {
                if (strpos($data->getName(), '__') !== 0) {
                    addWarning(PDERROR_PRIVATE_ASSUMED,'method',$data->class.'::'.$data->getName().'()');
                    $this->last->addKeyword('access','private');
                    $data->setDocBlock($this->last);
                }
            }
        }
        $data->setDocBlock($this->last);
        $data->path = $this->data->parent->path;
        $this->classes->addMethod($data);
        $this->last = false;
    }
    /**
     * handles post-parsing of functions
     *
     * This function sets {@link $data}->clean to false to tell the
     * phpDocumentor_IntermediateParser that a page-level DocBlock can't be
     * found after this point on this page.  It then sets the package to be the
     * same as the page, aligns the docblock's @param, @global, and @staticvar
     * tags with the information parsed from the function source code.
     *
     * If source code has been parsed by a {@}source} tag, the source is added
     * to its docblock, and then the parserFunction adds itself to the
     * {@link ProceduralPages} class
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserFunction $data
     */
    function handleFunction($event,$data)
    {
        if ($this->_lastDocBlockWasPageLevel)
        {
            addWarning(PDERROR_DOCBLOCK_CONFLICT, 'function', $data->getName());
            if (!$this->_oldPageLevel)
            {
                unset($this->last);
            }
        }
        $this->_lastDocBlockWasPageLevel = false;
        $this->data->clean = false;
        if ($this->private_page)
        {
            unset($this->last);
            return;
        }
        if (empty($this->last))
        {
            if (isset($this->db_template))
            {
                // use the docblock template
                $this->last = phpDocumentor_clone($this->db_template);
            }
            else
            {
                // we don't have a docblock, create an empty one to get rid of errors
                $this->last = new parserDocblock();
            }
        }
//        $this->last->setLineNumber($data->getLineNumber());
        if ($this->last->getKeyword('ignore'))
        {
            unset($this->last);
            return;
        }
        $this->last->overridePackage($this->category,$this->package,$this->subpackage,$data->getName(),'function');
        foreach($data->listParams() as $key => $param)
        {
            $update_params[$key] = $param;
        }
        foreach($data->listGlobals() as $param)
        {
            $update_globals[] = $param[1];
        }
        foreach($data->listStatics() as $param)
        {
            $update_statics[] = $param[0];
        }
        if (isset($update_params))
        $this->last->updateParams($update_params);
        if (isset($update_globals))
        $this->last->updateGlobals($update_globals);
        if (isset($update_statics))
        $this->last->updateStatics($update_statics);
        unset($update_params);
        unset($update_globals);
        unset($update_statics);
        if ($data->hasSource())
        {
            $this->last->setSource($data->getSource());
        }
        if (count($this->last->params) == 1 && !count($data->listParams()))
        {
            // if the function has no parameters, and 1 @param, add it to the list as optional, default value is description from @param
            $pars = $this->last->listParams();
            $data->addParam($pars[0]['var'],$pars[0]['data']->getString());
        }
        $data->setDocBlock($this->last);
        $this->proceduralpages->addFunction($data);
        $this->last = false;
    }
    
    /**
     * handles post-parsing of defines
     *
     * This function sets {@link $data}->clean to false to tell the
     * phpDocumentor_IntermediateParser that a page-level DocBlock can't be
     * found after this point on this page.  It then sets the package to be the
     * same as the page and adds itself to the {@link ProceduralPages} class
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserDefine $data
     */
    function handleDefine($event,$data)
    {
        if ($this->_lastDocBlockWasPageLevel)
        {
            addWarning(PDERROR_DOCBLOCK_CONFLICT, 'define', $data->getName());
            if (!$this->_oldPageLevel)
            {
                unset($this->last);
            }
        }
        $this->_lastDocBlockWasPageLevel = false;
        $this->data->clean = false;
        if ($this->private_page)
        {
            unset($this->last);
            return;
        }
        if (empty($this->last))
        {
            if (isset($this->db_template))
            {
                // use the docblock template
                $this->last = phpDocumentor_clone($this->db_template);
            }
            else
            {
                // we don't have a docblock, create an empty one to get rid of errors
                $this->last = new parserDocblock();
            }
        }
//        $this->last->setLineNumber($data->getLineNumber());
        if ($this->last->getKeyword('ignore'))
        {
            unset($this->last);
            return;
        }
        $this->last->overridePackage($this->category,$this->package,$this->subpackage,$data->getName(),'define');
        $data->setDocBlock($this->last);
        $this->proceduralpages->addDefine($data);
        $this->last = false;
    }
    
    /**
     * handles post-parsing of classes
     *
     * This function sets {@link $data}->clean to false to tell the
     * phpDocumentor_IntermediateParser that a page-level DocBlock can't be
     * found after this point on this page.  It sets {@link $cur_class} to its
     * name, and if an @ignore tag is found in the DocBlock, it sets
     * {@link $private_class} to true, to prevent post-parsing of any of the
     * class's vars or methods.  Then it checks for the existence of a package
     * page for the class's package
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserClass $data
     */
    function handleClass($event,$data)
    {
        global $_phpDocumentor_setting;
        if ($data->isInterface()) 
        {
            $objectType = 'interface';
        }
        else
        {
            $objectType = 'class';
        }
        if ($this->_lastDocBlockWasPageLevel)
        {
            if (!$this->_oldPageLevel)
            {
                addWarning(PDERROR_DOCBLOCK_GOES_CLASS, $data->getName());
                $doc = new parserDocBlock;
                $doc->category = $this->category;
                $doc->package = $this->package;
                $doc->subpackage = $this->subpackage;
                if ($_phpDocumentor_setting['sourcecode']) {
                    $doc->canSource();
                    $doc->addFileSource($this->data->parent->path, $this->data->parent->source);
                }
                $this->data->setDocBlock($doc);
                unset($doc);
                if ($this->last) {
                    $this->last->cantSource();
                }
            }
        }
        $this->_lastDocBlockWasPageLevel = false;
        $this->data->clean = false;
        if (empty($this->last))
        {
            if ($this->undocumentedElementWarnings)
            {
                addWarning(PDERROR_UNDOCUMENTED_ELEMENT,'Class',$data->getName(),'Class');
            }
            if (isset($this->db_template))
            {
                // use the docblock template
                $this->last = phpDocumentor_clone($this->db_template);
            }
            else
            {
                // we don't have a docblock, create an empty one to get rid of errors
                $this->last = new parserDocblock();
            }
            list($this->last->package, $this->last->subpackage) = $this->_guessPackage($this->data->parent->path, $this->data->parent->getSourceLocation('dummy'));
            addWarning(PDERROR_NO_PACKAGE_TAG,$objectType,$data->getName(),$this->last->package);
        } else
        {
            if (!$this->last->getExplicitPackage())
            {
                list($this->last->package, $this->last->subpackage) = $this->_guessPackage($this->data->parent->path, $this->data->parent->getSourceLocation('dummy'));
                addWarning(PDERROR_NO_PACKAGE_TAG,$objectType,$data->getName(),$this->last->package);
            } else
            {
                if (isset($this->packagecategories[$this->package])
                    && $this->packagecategories[$this->package] != $this->category)
                    addWarning(PDERROR_PACKAGECAT_SET,$this->package,
                                $this->packagecategories[$this->package],
                                $this->category);
                $this->packagecategories[$this->package] = $this->category;
            }
        }
        $this->last->updateModifiers($data->getModifiers());
//        $this->last->setLineNumber($data->getLineNumber());
        $data->setDocBlock($this->last);
        $this->cur_class = $name = $data->getName();
        if ($this->last->getKeyword('ignore'))
        {
            $this->private_class = true;
            unset($this->last);
            return;
        }
        $data->path = $this->data->parent->path;
        $this->classes->addClass($data);
        $this->private_class = false;
        if ($this->last->package)
        {
            $this->parsePackagePage($this->last->package, $this->data->parent->getPath());
        }
        $this->last = false;
    }
    
    /**
     * handles post-parsing of procedural pages
     *
     * this event is called at the start of a new page, before the Parser knows
     * whether the page will contain any procedural pages or not
     * @param integer $event Event number from {@link Parser.inc}
     * @param parserPage $data
     */
    function handlePage($event,$data)
    {
        $type = 'page';
        $this->private_page = false;
        $this->data = new parserData;
        $data->category = $this->category = $GLOBALS['phpDocumentor_DefaultCategoryName'];
        $this->package = $GLOBALS['phpDocumentor_DefaultPackageName'];
        $this->subpackage = '';
        $this->proceduralpages->addPage($data);
        $this->data->setParent($data);
        $this->pages[$data->getPath()] = $this->data;
        $this->classes->nextFile($data->getPath());
        $this->packageoutput = $data->getPackageOutput();
    }
    
    /**
     * handles post-parsing of DocBlocks
     *
     * This function sets {@link $last} to the DocBlock represented by $data, to
     * allow the next documentable element passed to
     * phpDocumentor_IntermediateParser to link the DocBlock into its $docblock
     * property.  This function also checks for two special cases of DocBlocks:
     *