* @copyright 2002-2008 Gregory Beaver
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @version Release: 1.4.4
* @link http://www.phpdoc.org
* @link http://pear.php.net/PhpDocumentor
* @since 1.1
* @todo CS cleanup - change package to PhpDocumentor
*/
class ProceduralPages
{
/**
* file being parsed, used in every add function
* to match up elements with the file that contains them
*
* @see addClass(), addMethod(), addVar(), nextFile()
* @var string
*/
var $curfile;
/**
* array of all procedural pages ordered by name
* Format:
*
* array(
* name => array(
* fullpath => parserPage,
* fullpath => parserPage2 [if there are name conflicts],
* ...
* )
* )
*
*
* @var array
*/
var $pages = array();
/**
* array of all procedural pages ordered by name
* that have been ignored via -po or @access private or @ignore
* Format:
*
* array(
* name => array(
* fullpath => parserPage,
* fullpath => parserPage2 [if there are name conflicts],
* ...
* )
* )
*
*
* @var array
*/
var $ignorepages = array();
/**
* array of all procedural page names ordered by full path to the file
* Format:
*
* array(
* fullpath => name
* )
*
*
* @var array
*/
var $pathpages = array();
/**
* array of parsed includes organized by the full path
* of the file that contains the include.
* Format:
*
* array(
* full path => array(
* includename => {@link parserInclude}
* )
* )
*
*
* @var array
*/
var $includesbyfile = array();
/**
* array of parsed functions organized by the full path
* of the file that contains the function.
* Format:
*
* array(
* full path => array(
* functionname => {@link parserFunction}
* )
* )
*
*
* @var array
*/
var $functionsbyfile = array();
/**
* array of parsed defines organized by the full path
* of the file that contains the define.
* Format:
*
* array(
* full path => array(
* definename => {@link parserDefine}
* )
* )
*
*
* @var array
*/
var $definesbyfile = array();
/**
* array of parsed global variables organized by the full path
* of the file that contains the global variable definition.
* Format:
*
* array(
* full path => array(
* globalname => {@link parserGlobal}
* )
* )
*
*
* @var array
*/
var $globalsbyfile = array();
/**
* array of file names organized by functions that are in the file.
*
* This structure is designed to handle name conflicts. Two files can contain
* functions with the same name, and this array will record both filenames to
* help control namespace errors
* Format:
*
* array(
* functionname => array(
* full path of file containing functionname,
* full path of file 2 containing functionname,
* ...
* )
* )
*
*
* @var array
*/
var $functionsbynamefile = array();
/**
* array of file names organized by defines that are in the file.
* This structure is designed to handle name conflicts. Two files
* can contain defines with the same name, and this array will
* record both filenames to help control namespace errors
* Format:
*
* array(
* definename => array(
* full path of file containing definename,
* full path of file 2 containing definename,
* ...
* )
* )
*
*
* @var array
*/
var $definesbynamefile = array();
/**
* array of file names organized by global variables that are in the file.
*
* This structure is designed to handle name conflicts. Two files can
* contain global variables with the same name, and this array will
* record both filenames to help control namespace errors
* Format:
*
* array(
* global variablename => array(
* full path of file containing global variablename,
* full path of file 2 containing global variablename,
* ...
* )
* )
*
*
* @var array
*/
var $globalsbynamefile = array();
/**
* array of packages ordered by full path
* Format:
*
* array(
* fullpath => array(
* packagename,
* subpackagename
* )
* )
*
*
* @var array
*/
var $pagepackages = array();
/**
* array of packages assigned to classes in a file, ordered by fullpath
* Format:
*
* array(
* fullpath => array(
* packagename => array(
* subpackagename => 1,
* subpackagename => 1,
* ..
* ),
* packagename2 => array(...
* )
* )
* )
*
*
* @var array
*/
var $pageclasspackages = array();
/**
* Namespace conflicts within all documented packages of functions
* Format:
*
* array(
* functionname => array(
* full path,
* full path,
* ...
* )
* )
*
*
* @var array
*/
var $functionconflicts = array();
/**
* Namespace conflicts within all documented pages
* Format:
*
* array(
* pagename => array(
* fullpath,
* fullpath,
* ...
* )
* )
*
*
* @var array
*/
var $pageconflicts = array();
/**
* Namespace conflicts within all documented packages of functions
* Format:
*
* array(
* functionname => array(
* full path,
* full path,
* ...
* )
* )
*
*
* @var array
*/
var $defineconflicts = array();
/**
* Namespace conflicts within all documented packages of functions
* Format:
*
* array(
* functionname => array(
* full path,
* full path,
* ...
* )
* )
*
*
* @var array
*/
var $globalconflicts = array();
/**
* @access private
* @var array
*/
var $revcpbf = array();
/**
* @access private
* @var boolean
*/
var $packagesetup = false;
/**
* sets up the {@link $pages} array
*
* @param parserPage &$element the parser page element
*
* @return void
*/
function addPage(&$element)
{
$this->curfile
= $element->getPath();
$this->pages[$element->getFile()][$element->getPath()]
= $element;
$this->pathpages[$this->curfile]
= $element->getFile();
$this->addPagePackage($this->curfile,
$element->package, $element->subpackage);
}
/**
* moves a page from the {@link $pages} array to the {@link $ignorepages} array
*
* @param parserPage &$element the parser page element
*
* @return void
*/
function ignorePage(&$element)
{
$this->ignorepages[$element->getFile()][$element->getPath()]
= $this->pages[$element->getFile()][$element->getPath()];
unset($this->pages[$element->getFile()][$element->getPath()]);
}
/**
* gathers path-related info about a given element
*
* @param string $path path to the element
* @param mixed &$c ???
*
* @return array|bool an array of path info,
* or FALSE
* @todo figure out what &$c is and update the param tag
*/
function getPathInfo($path, &$c)
{
$path = str_replace('/', SMART_PATH_DELIMITER, $path);
$info = array();
if (!isset($this->pathpages[$path])) {
return false;
}
$p = $this->pages[$this->pathpages[$path]][$path];
// fixes [ 1391432 ] Too many underscores in include links.
$p->name = $p->origName;
$p->name = $c->getPageName($p);
$info['package'] = $p->package;
$info['subpackage'] = $p->subpackage;
$info['name'] = $p->getFile();
$info['source_loc'] = $p->getSourceLocation($c);
$x = new pageLink;
$x->addLink($p->path, $p->name, $p->file, $p->package, $p->subpackage);
$info['docs'] = $c->returnSee($x);
$p->name = $p->origName;
return $info;
}
/**
* Change a page's name from its file to alias $name
*
* This function is used to handle a @name tag in a page-level DocBlock
*
* @param string $name the alias
*
* @return void
*/
function setName($name)
{
if ($this->pages[$name][$this->curfile]->file == $name) {
addWarning(PDERROR_NAME_ALIAS_SAME_AS_TARGET,'');
} else {
$this->pages[$name][$this->curfile]
= $this->pages[$this->pathpages[$this->curfile]][$this->curfile];
$this->pages[$name][$this->curfile]->file
= $name;
unset($this->pages[$this->pathpages[$this->curfile]][$this->curfile]);
$this->pathpages[$this->curfile] = $name;
}
}
/**
* Changes the package of the page represented by $path
*
* changes package in both the {@link $pages} array
* and the {@link pagepackages} array
*
* @param string $path full path
* @param string $package the package name
* @param string $subpackage the subpackage name
*
* @return void
*/
function addPagePackage($path, $package, $subpackage)
{
$this->pages[$this->pathpages[$path]][$path]->package
= $package;
$this->pages[$this->pathpages[$path]][$path]->subpackage
= $subpackage;
$this->pagepackages[$path]
= array($package, $subpackage);
if (isset($this->includesbyfile[$path])) {
foreach ($this->includesbyfile[$path] as $i => $el) {
$el->package = $package;
$el->subpackage = $subpackage;
$this->includesbyfile[$path][$i] = $el;
}
}
if (isset($this->functionsbyfile[$path])) {
foreach ($this->functionsbyfile[$path] as $i => $el) {
$el->package = $package;
$el->subpackage = $subpackage;
$this->functionsbyfile[$path][$i] = $el;
}
}
if (isset($this->definesbyfile[$path])) {
foreach ($this->definesbyfile[$path] as $i => $el) {
$el->package = $package;
$el->subpackage = $subpackage;
$this->definesbyfile[$path][$i] = $el;
}
}
if (isset($this->globalsbyfile[$path])) {
foreach ($this->globalsbyfile[$path] as $i => $el) {
$el->package = $package;
$el->subpackage = $subpackage;
$this->globalsbyfile[$path][$i] = $el;
}
}
}
/**
* sets up the {@link $includesbyfile} array using {@link $curfile}
*
* @param parserInclude &$element the "include" element object
*
* @return void
*/
function addInclude(&$element)
{
$this->includesbyfile[$this->curfile][] = $element;
}
/**
* sets up the {@link $functionsbyfile} array using {@link $curfile}
*
* @param parserFunction &$element the "function" object
*
* @return void
*/
function addFunction(&$element)
{
if (isset($this->functionsbyfile[$this->curfile])) {
foreach ($this->functionsbyfile[$this->curfile] as $i => $function) {
if ($function->getName() == $element->getName()) {
addWarning(PDERROR_ELEMENT_IGNORED, 'function',
$element->getName(), $this->curfile);
return;
}
}
}
$this->functionsbyfile[$this->curfile][] = $element;
$this->functionsbynamefile[$element->getName()][] = $this->curfile;
}
/**
* sets up the {@link $globalsbyfile} array using {@link $curfile}
*
* @param parserGlobal &$element the "global" element
*
* @return void
*/
function addGlobal(&$element)
{
if (isset($this->globalsbyfile[$this->curfile])) {
foreach ($this->globalsbyfile[$this->curfile] as $i => $global) {
if ($global->getName() == $element->getName()) {
addWarning(PDERROR_ELEMENT_IGNORED, 'global variable',
$element->getName(), $this->curfile);
return;
}
}
}
$this->globalsbyfile[$this->curfile][] = $element;
$this->globalsbynamefile[$element->getName()][] = $this->curfile;
}
/**
* sets up the {@link $definesbyfile} array using {@link $curfile}
*
* @param parserDefine &$element the "define" element
*
* @return void
*/
function addDefine(&$element)
{
if (isset($this->definesbyfile[$this->curfile])) {
foreach ($this->definesbyfile[$this->curfile] as $i => $define) {
if ($define->getName() == $element->getName()) {
addWarning(PDERROR_ELEMENT_IGNORED, 'define',
$element->getName(), $this->curfile);
return;
}
}
}
$this->definesbyfile[$this->curfile][] = $element;
$this->definesbynamefile[$element->getName()][] = $this->curfile;
}
/**
* Used to align an element with the package of its parent page
* prior to Conversion.
*
* @param parserElement &$element the element to align
*
* @return void
*/
function replaceElement(&$element)
{
if ($element->type == 'define') {
foreach ($this->definesbyfile[$element->getPath()] as $i => $el) {
if ($el->getName() == $element->getName()) {
$this->definesbyfile[$element->getPath()][$i] = &$element;
}
}
} elseif ($element->type == 'global') {
foreach ($this->globalsbyfile[$element->getPath()] as $i => $el) {
if ($el->getName() == $element->getName()) {
$this->globalsbyfile[$element->getPath()][$i] = &$element;
}
}
} elseif ($element->type == 'include') {
foreach ($this->includesbyfile[$element->getPath()] as $i => $el) {
if ($el->getName() == $element->getName()) {
$this->includesbyfile[$element->getPath()][$i] = &$element;
}
}
} elseif ($element->type == 'function') {
foreach ($this->functionsbyfile[$element->getPath()] as $i => $el) {
if ($el->getName() == $element->getName()) {
$this->functionsbyfile[$element->getPath()][$i] = &$element;
}
}
}
}
/**
* adds a package from a class to the current file
*
* @param string $file full path to the file that contains the class
* @param string $package package name
* @param string $subpackage subpackage name
*
* @return void
*/
function addClassPackageToFile($file, $package, $subpackage)
{
if (!isset($this->revcpbf[$file][$package][$subpackage])) {
$this->pageclasspackages[$file][$package][$subpackage] = 1;
}
$this->revcpbf[$file][$package][$subpackage] = 1;
}
/**
* if there is one class package in a file,
* the parent path inherits the package if its package is default.
* helps with -po to avoid dumb bugs
*
* @return void
*/
function setupPagePackages()
{
if ($this->packagesetup) {
return;
}
foreach ($this->pageclasspackages as $fullpath => $packages) {
if (isset($this->pagepackages[$fullpath])) {
if ($this->pagepackages[$fullpath][0]
== $GLOBALS['phpDocumentor_DefaultPackageName']
) {
if (count($packages) == 1) {
list($package, $subpackage) = each($packages);
if (count($subpackage) == 1) {
list($subpackage,) = each($subpackage);
} else {
$subpackage = '';
}
$this->addPagePackage($fullpath, $package, $subpackage);
}
}
}
}
$this->packagesetup = true;
}
/**
* extracts function, define, and global variable name conflicts within the
* same package and between different packages. No two elements with the same
* name are allowed in the same package, to keep automatic linking possible.
*
* @param mixed &$render the renderer object
*
* @return void
* @access private
* @todo functions, defines, and globals are coded,
* but pages section is empty... does it need to be coded?
*/
function setupConflicts(&$render)
{
foreach ($this->functionsbynamefile as $function => $paths) {
if (count($paths) - 1) {
//conflict
$package = array();
foreach ($paths as $path) {
// create a list of conflicting functions in each package
$package[$this->pagepackages[$path][0]][] = $path;
}
foreach ($package as $pathpackages) {
// if at least 2 functions exist in the same package,
// delete all but the first one and add warnings
if (count($pathpackages) - 1) {
for ($i=1; $i < count($pathpackages); $i++) {
addWarning(PDERROR_ELEMENT_IGNORED, 'function',
$function, $pathpackages[$i]);
foreach ($this->functionsbyfile[$pathpackages[$i]]
as $j => $blah
) {
if ($this->functionsbyfile[$pathpackages[$i]][$j]
->getName() == $function
) {
unset($this
->functionsbyfile[$pathpackages[$i]][$j]);
}
}
$oth = array_flip($paths);
unset($paths[$oth[$pathpackages[$i]]]);
}
}
}
$this->functionconflicts[$function] = $paths;
}
}
foreach ($this->definesbynamefile as $define => $paths) {
if (count($paths) - 1) {
//conflict
$package = array();
foreach ($paths as $path) {
// create a list of conflicting functions in each package
$package[$this->pagepackages[$path][0]][] = $path;
}
foreach ($package as $pathpackages) {
// if at least 2 functions exist in the same package,
// delete all but the first one and add warnings
if (count($pathpackages) - 1) {
for ($i=1; $i < count($pathpackages); $i++) {
addWarning(PDERROR_ELEMENT_IGNORED, 'define',
$define, $pathpackages[$i]);
foreach ($this->definesbyfile[$pathpackages[$i]]
as $j => $blah
) {
if ($this->definesbyfile[$pathpackages[$i]][$j]
->getName() == $define
) {
unset($this
->definesbyfile[$pathpackages[$i]][$j]);
}
}
$oth = array_flip($paths);
unset($paths[$oth[$pathpackages[$i]]]);
}
}
}
$this->defineconflicts[$define] = $paths;
}
}
foreach ($this->globalsbynamefile as $global => $paths) {
if (count($paths) - 1) {
//conflict
$package = array();
foreach ($paths as $path) {
// create a list of conflicting functions in each package
$package[$this->pagepackages[$path][0]][] = $path;
}
foreach ($package as $pathpackages) {
// if at least 2 functions exist in the same package,
// delete all but the first one and add warnings
if (count($pathpackages) - 1) {
for ($i=1; $i < count($pathpackages); $i++) {
addWarning(PDERROR_ELEMENT_IGNORED, 'global variable',
$global, $pathpackages[$i]);
foreach ($this->globalsbyfile[$pathpackages[$i]]
as $j => $blah
) {
if ($this->globalsbyfile[$pathpackages[$i]][$j]
->getName() == $global
) {
unset($this
->globalsbyfile[$pathpackages[$i]][$j]);
}
}
$oth = array_flip($paths);
unset($paths[$oth[$pathpackages[$i]]]);
}
}
}
$this->globalconflicts[$global] = $paths;
}
}
/*
* @todo does this section still need to be coded???
*/
foreach ($this->pages as $name => $pages) {
if (count($pages) - 1) {
// possible conflict
}
}
}
/**
* called by {@link parserFunction::getConflicts()} to get
* inter-package conflicts, should not be called directly
*
* @param string $name the function name to check
*
* @access private
* @return array|bool Format: (package => {@link parserFunction}
* of conflicting function)
* or FALSE if the function is not recorded as a conflict
*/
function getFuncConflicts($name)
{
if (!isset($this->functionconflicts[$name])) {
return false;
}
$a = array();
foreach ($this->functionconflicts[$name] as $conflict) {
foreach ($this->functionsbyfile[$conflict] as $i => $func) {
if ($func->getName() == $name) {
$a[$this->functionsbyfile[$conflict][$i]->docblock->package]
= $this->functionsbyfile[$conflict][$i];
}
}
}
return $a;
}
/**
* called by {@link parserGlobal::getConflicts()}
* to get inter-package conflicts, should not be called directly
*
* @param string $name the global name to check
*
* @access private
* @return array|bool Format: (package => {@link parserGlobal}
* of conflicting global variable)
* or FALSE if the global is not recorded as a conflict
*/
function getGlobalConflicts($name)
{
if (!isset($this->globalconflicts[$name])) {
return false;
}
$a = array();
foreach ($this->globalconflicts[$name] as $conflict) {
foreach ($this->globalsbyfile[$conflict] as $i => $func) {
if ($func->getName() == $name) {
$a[$this->globalsbyfile[$conflict][$i]->docblock->package]
= $this->globalsbyfile[$conflict][$i];
}
}
}
return $a;
}
/**
* called by {@link parserDefine::getConflicts()}
* to get inter-package conflicts, should not be called directly
*
* @param string $name the define name to check
*
* @access private
* @return array|bool Format: (package => {@link parserDefine}
* of conflicting define)
* or FALSE if the define is not recorded as a conflict
*/
function getDefineConflicts($name)
{
if (!isset($this->defineconflicts[$name])) {
return false;
}
$a = array();
foreach ($this->defineconflicts[$name] as $conflict) {
foreach ($this->definesbyfile[$conflict] as $i => $func) {
if ($func->getName() == $name) {
$a[$this->definesbyfile[$conflict][$i]->docblock->package]
= $this->definesbyfile[$conflict][$i];
}
}
}
return $a;
}
/**
* Adjusts packages of all pages and removes name conflicts within a package
*
* Automatic linking requires that each linkable name have exactly one element
* associated with it. In other words, there cannot be two functions named
* foo() in the same package.
*
* This also adheres to php rules with one exception:
*
*
* if ($test == 3) {
* define('whatever', 'this thing');
* } else {
* define('whatever', 'this other thing');
* }
*
*
* phpDocumentor is not aware of conditional control structures because it
* would slow things down considerably. So, what phpDocumentor does is
* automatically ignore the second define and raise a warning. The warning can
* be eliminated with an @ignore tag on the second element like so:
*
*
* if ($test == 3) {
* define('whatever', 'this thing');
* } else {
* /**
* * @ignore
* {@*}
* define('whatever', 'this other thing');
* }
*
*
* If there are two files that contain the same procedural elements in the
* same package (for example, a common configuration file common.php), they
* will also be ignored as if they were in the same file. The reasoning
* behind this is simple. A package is an indivisible set of files and
* classes that a user will include in their code. Name conflicts must be
* avoided to allow successful execution.
*
* This function also plays the all-important role of calling
* {@link phpDocumentor_IntermediateParser::addElementToPage()} in order to add
* processed elements to their pages for Conversion.
*
* @param phpDocumentor_IntermediateParser &$render the parser
*
* @return void
*/
function setupPages(&$render)
{
global $_phpDocumentor_setting;
phpDocumentor_out("\nProcessing Procedural Page Element Name Conflicts\n\n");
flush();
$this->setupPagePackages();
$this->setupConflicts($render);
// phpDocumentor_out("\nProcessing Procedural Pages\n\n");
foreach ($this->pathpages as $path => $name) {
// phpDocumentor_out("Processing $path\n");
$a = $this->pagepackages[$path];
$b = &$this->pages[$name][$path];
$render->addPage($b, $path);
$render->addUses($b, $path);
if (isset($this->includesbyfile[$path])) {
foreach ($this->includesbyfile[$path] as $include) {
$include->docblock->package = $a[0];
$include->docblock->subpackage = $a[1];
$render->addElementToPage($include, $path);
}
}
if (isset($this->functionsbyfile[$path])) {
foreach ($this->functionsbyfile[$path] as $function) {
$function->docblock->package = $a[0];
$function->docblock->subpackage = $a[1];
$render->addElementToPage($function, $path);
$render->addUses($function, $path);
}
}
if (isset($this->definesbyfile[$path])) {
foreach ($this->definesbyfile[$path] as $define) {
$define->docblock->package = $a[0];
$define->docblock->subpackage = $a[1];
$render->addElementToPage($define, $path);
$render->addUses($define, $path);
}
}
if (isset($this->globalsbyfile[$path])) {
foreach ($this->globalsbyfile[$path] as $global) {
$global->docblock->package = $a[0];
$global->docblock->subpackage = $a[1];
$render->addElementToPage($global, $path);
$render->addUses($global, $path);
}
}
}
}
/**
* sets the parser base
*
* @param mixed $pbase the parser base
*
* @return void
*/
function setParseBase($pbase)
{
$this->_parsedbase = $pbase;
}
/**
* checks to see if the parsed file matches the given path
*
* @param string $path the path to look for
* @param string $infile the file to check
*
* @return parserPage|bool matched parserPage if found,
* or FALSE if not found
*/
function pathMatchesParsedFile($path, $infile)
{
$test = $this->getRealPath($path, $infile);
if (is_string($test)) {
if (isset($this->pathpages[$test])) {
return $this->pages[$this->pathpages[$test]][$test];
}
if (PHPDOCUMENTOR_WINDOWS) {
$test = str_replace('/', '\\', $test);
}
if (isset($this->pathpages[$test])) {
$a = $this->pages[$this->pathpages[$test]][$test];
if (is_array($a->packageOutput)
&& !in_array($a->package, $a->packageOutput)
) {
return false;
}
return $this->pages[$this->pathpages[$test]][$test];
}
} else {
foreach ($test as $file) {
if (isset($this->pathpages[$file])) {
return $this->pages[$this->pathpages[$file]][$file];
}
if (PHPDOCUMENTOR_WINDOWS) {
$file = str_replace('/', '\\', $file);
}
if (isset($this->pathpages[$file])) {
$a = $this->pages[$this->pathpages[$file]][$file];
if (is_array($a->packageOutput)
&& !in_array($a->package, $a->packageOutput)
) {
return false;
}
return $this->pages[$this->pathpages[$file]][$file];
}
}
}
return false;
}
/**
* Ensures the path to the file is an absolute path
*
* @param string $path path to the file
* @param string $file the file name
*
* @return array|string returns an array of possible file locations or
* a string if there is an exact match
*/
function getRealPath($path, $file)
{
$curdir = str_replace('\\', '/', dirname($file));
$path = str_replace('\\', '/', $path);
if (strpos($path, ':') !== false) {
// windows, and we have a drive letter
return $path;
} elseif (strpos($path, '/') === 0) {
return $path;
}
// not an absolute path
$path = explode('/', $path);
if ($path[0] == '.') {
$path[0] = dirname($file);
return join($path, '/');
} elseif ($path[0] == '..') {
$dirfile = explode('/', dirname(str_replace('\\', '/', $file)));
// remove the current directory
array_pop($dirfile);
if (!count($dirfile)) {
// we were at a top-level dir!
return false;
}
// replace .. with parent dirname
$path[0] = join($dirfile, '/');
return join($path, '/');
} else {
$path = join($path, '/');
return array($curdir . PATH_DELIMITER . $path,
str_replace('\\', '/', PHPDOCUMENTOR_BASE)
. PATH_DELIMITER . $path);
}
}
}
?>