<?php
/**
* This file is part of unicode class.
* It provides basic documentation.
*
* @autor Rubens Takiguti Ribeiro
* @date 2008-06-19
* @version 0.52 2008-06-26
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3 (LICENSE.TXT)
* @copyright Copyright (C) 2008 Rubens Takiguti Ribeiro
*/
require_once(dirname(__FILE__).'/unicode.class.php');
/* BEGIN */
global $DOC;
start_doc();
print_doc_page();
end_doc();
/* END */
/**
* Start the doc script. Data used in this script are saved in session.
*
* @return void
*/
function start_doc() {
global $DOC;
if (!extension_loaded('reflection')) {
die('Reflection extension is need to access this page.');
}
$DOC = new stdClass();
session_start();
if (isset($_SESSION['DOC_CLASS'])) {
$DOC = unserialize($_SESSION['DOC_CLASS']);
}
// Get filename
if (!isset($DOC->file)) {
$DOC->file = basename(__FILE__);
}
// Get action
if (isset($_GET['action'])) {
$DOC->action = $_GET['action'];
} elseif (!isset($DOC->action)) {
$DOC->action = 'main';
}
// Get data
if (!isset($DOC->data)) {
$DOC->data = array();
}
if (isset($_GET['data'])) {
foreach ($_GET['data'] as $key => $value) {
if ($value != 'null') {
$DOC->data[$key] = $value;
} else {
unset($DOC->data[$key]);
}
}
}
}
/**
* End the doc script (save session).
*
* @return void
*/
function end_doc() {
global $DOC;
$_SESSION['DOC_CLASS'] = serialize($DOC);
}
/**
* Print page based on user action.
*
* @return void
*/
function print_doc_page() {
global $DOC;
$last = 0;
foreach (get_classes(false) as $class) {
try {
$ref_class = new ReflectionClass($class);
} catch (Exception $e) {
continue;
}
$data = parse_comment($ref_class->getDocComment());
list($version, $date) = explode(' ', $data['@version']);
$date = date_parse($date);
$last = max($last, mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
}
header('Content-Type: text/html; charset=UTF-8');
echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
echo '<html lang="en">';
echo '<head>';
echo '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">';
echo '<title>Class Documentation</title>';
echo '<link rel="index" href="'.$DOC->file.'" title="Main" >';
echo '</head>';
echo '<body>';
echo '<h1 id="header">Class Documentation</h1>';
echo '<div id="content">';
echo '<hr />';
check_actions(); // Script Controller
echo '<hr />';
echo '</div>';
echo '<p id="footer">';
echo 'Copyright © 2008 Rubens Takiguti Ribeiro<br />';
echo "Last update: \n";
echo '$Date '.gmstrftime('%Y/%m/%d %H:%M:%S', $last)." $\n";
echo '</p>';
echo '</body>';
echo '</html>';
}
/**
* Check and invoke action requests.
*
* @return void
*/
function check_actions() {
global $DOC;
switch ($DOC->action) {
case 'open_class':
case 'class_description':
print_class_description();
break;
case 'class_structure':
print_class_structure();
break;
case 'class_constants':
print_class_constants();
break;
case 'class_properties':
print_class_properties();
break;
case 'class_methods':
print_class_methods();
break;
case 'main':
default:
$DOC->data = array(); // Clear data
print_main();
break;
}
}
/**
* Print the main page.
*
* @return void
*/
function print_main() {
global $DOC;
echo '<h2>Main Page</h2>';
echo '<p>Select a class below.</p>';
echo '<ol>';
foreach (get_classes() as $class => $name) {
echo '<li><a href="'.$DOC->file.'?action=open_class&data[class]='.$class.'">'.$name.'</a></li>';
}
echo '</ol>';
}
/**
* Print class menu.
*
* @return void
*/
function print_class_menu() {
global $DOC;
$file = &$DOC->file;
$actions = array('main' => 'Main',
'class_description' => 'Description',
'class_structure' => 'Structure',
'class_constants' => 'Constants',
'class_properties' => 'Properties',
'class_methods' => 'Methods');
echo '<h2>'.$DOC->data['class'].'</h2>';
echo '<ul class="navbar">';
foreach ($actions as $action => $name) {
$class = $DOC->action == $action ? ' class="active"' : '';
echo '<li><a href="'.$file.'?action='.$action.'"'.$class.'>'.$name.'</a></li>';
}
echo '</ul>';
}
/**
* Print class description.
*
* @return void;
*/
function print_class_description() {
global $DOC;
if (!isset($DOC->data['class'])) {
echo '<p class="error">Class not defined</p>';
print_main();
return;
}
try {
$ref_class = new ReflectionClass($DOC->data['class']);
} catch (Exception $e) {
echo '<p class="error">Class not defined: '.$e->getMessage().'</p>';
print_main();
return;
}
$data = parse_comment($ref_class->getDocComment());
$extension = $ref_class->getExtensionName();
if (empty($extension)) {
$extension = null;
}
$interfaces = array();
foreach ($ref_class->getInterfaces() as $interface) {
$interfaces[] = $interface->getName();
}
$modifiers = implode(' ', Reflection::getModifierNames($ref_class->getModifiers()));
if (empty($modifiers)) {
$modifiers = null;
}
$data['@extension'] = $extension;
$data['@class'] = $ref_class->getName();
$data['@file'] = $ref_class->getFileName();
$data['@lines'] = $ref_class->getStartLine().'-'.$ref_class->getEndLine();
$data['@implements'] = $interfaces;
$data['@instantiable'] = (bool)$ref_class->isInstantiable();
$data['@interface'] = (bool)$ref_class->isInterface();
$data['@abstract'] = (bool)$ref_class->isAbstract();
$data['@final'] = (bool)$ref_class->isFinal();
$data['@iterateable'] = (bool)$ref_class->isIterateable();
$data['@modifiers'] = $modifiers;
print_class_menu();
echo '<h3>Basic Class Description</h3>';
print_table($data, 'Basic Class Description');
}
/**
* Print class structure.
*
* @return void
*/
function print_class_structure() {
global $DOC;
if (!isset($DOC->data['class'])) {
echo '<p class="error">Class not defined</p>';
print_main();
return;
}
$ref_class = new ReflectionClass($DOC->data['class']);
$comments = get_category_comments($ref_class->getFileName());
print_class_menu();
echo '<h3>Class Structure</h3>';
if (count($comments)) {
print_list($comments);
} else {
echo '<p>No structure specified in this file.</p>';
return;
}
}
/**
* Print class constants.
*
* @return void
*/
function print_class_constants() {
global $DOC;
if (!isset($DOC->data['class'])) {
echo '<p class="error">Class not defined</p>';
print_main();
return;
}
$ref_class = new ReflectionClass($DOC->data['class']);
$constants = $ref_class->getConstants();
print_class_menu();
echo '<h3>Class Constants</h3>';
if (!count($constants)) {
echo '<p>No constants avaliable.</p>';
return;
}
echo '<table>';
echo '<caption>Class Constants</caption>';
echo '<thead>';
echo ' <tr>';
echo ' <th scope="col">Constant</th>';
echo ' <th scope="col">Value</th>';
echo ' <th scope="col">Type</th>';
echo ' </tr>';
echo '</thead>';
echo '<tbody>';
foreach ($constants as $constant => $value) {
echo '<tr>';
echo ' <th scope="row">'.$DOC->data['class'].'::'.$constant.'</th>';
echo ' <td>'.convert_php_value($value).'</td>';
echo ' <td>'.gettype($value).'</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
}
/**
* Print class properties.
*
* @return void
*/
function print_class_properties() {
global $DOC;
if (!isset($DOC->data['class'])) {
echo '<p class="error">Class not defined</p>';
print_main();
return;
}
$ref_class = new ReflectionClass($DOC->data['class']);
$default_properties = $ref_class->getDefaultProperties();
print_class_menu();
if (!count($ref_class->getProperties())) {
echo '<p>No properties avaliable.</p>';
return;
}
$properties = array();
echo '<h3>Class Properties</h3>';
echo '<ol>';
foreach ($ref_class->getProperties() as $property) {
$data = parse_comment($property->getDocComment());
$data['@name'] = $property->getName();
if ($property->isPublic()) {
$data['@visibility'] = 'public';
} elseif ($property->isProtected()) {
$data['@visibility'] = 'protected';
} elseif ($property->isPrivate()) {
$data['@visibility'] = 'private';
} else {
$data['@visibility'] = 'undefined';
}
$data['@static'] = $property->isStatic();
$default = $default_properties[$data['@name']];
$data['@default'] = convert_php_value($default).' ('.gettype($default).')';
echo ' <li><a href="#prop_'.$data['@name'].'">'.$data['@name'].'</a></li>';
$properties[] = $data;
}
echo '</ol>';
echo '<hr />';
echo '<h3>Properties Details</h3>';
foreach ($properties as $property) {
echo '<div>';
echo '<h4><a id="prop_'.$property['@name'].'">'.$property['@name'].'</a></h4>';
print_table($property);
echo '</div>';
}
}
/**
* Print class methods.
*
* @return void
*/
function print_class_methods() {
global $DOC;
if (!isset($DOC->data['class'])) {
echo '<p class="error">Class not defined</p>';
print_main();
return;
}
$ref_class = new ReflectionClass($DOC->data['class']);
$comments = get_category_comments($ref_class->getFileName());
$first_method_line = false;
foreach ($ref_class->getMethods() as $method) {
$data = parse_comment($method->getDocComment());
$data['@name'] = $method->getName();
if ($method->isPublic()) {
$data['@visibility'] = 'public';
} elseif ($method->isProtected()) {
$data['@visibility'] = 'protected';
} elseif ($method->isPrivate()) {
$data['@visibility'] = 'private';
} else {
$data['@visibility'] = 'undefined';
}
$data['@static'] = $method->isStatic();
$data['@final'] = $method->isFinal();
$data['@abstract'] = $method->isAbstract();
$data['@returns reference'] = $method->returnsReference();
$data['@lines'] = $method->getStartLine().'-'.$method->getEndLine();
$parameters = array();
foreach ($method->getParameters() as $parameter) {
$p = '$'.$parameter->getName();
if ($parameter->isPassedByReference()) {
$p = '&'.$p;
}
if ($parameter->isDefaultValueAvailable()) {
$p .= ' = '.convert_php_value($parameter->getDefaultValue());
}
$parameters[] = $p;
}
$data['@parameters'] = $parameters;
if ($comments) {
$data['@category'] = get_category($comments, $method->getStartLine());
}
$methods[$data['@name']] = $data;
}
print_class_menu();
echo '<h3>Class Methods</h3>';
if (!count($methods)) {
echo '<p>No methods avaliable.</p>';
return;
}
echo '<ul>';
if (!count($comments)) {
foreach ($methods as $method) {
echo '<li><a href="#method_'.$method['@name'].'">'.$method['@name'].'</a></li>';
}
} else {
$category = array();
foreach ($methods as $method) {
if (!isset($category[$method['@category']])) {
if (count($category)) {
echo '</ul>';
}
echo '<li><strong>'.$method['@category'].'</strong><ul>';
$category[$method['@category']] = true;
}
echo '<li><a href="#method_'.$method['@name'].'">'.$method['@name'].'</a></li>';
}
echo '</ul>';
}
echo '</ul>';
echo '<hr />';
echo '<h3>Methods Details</h3>';
foreach ($methods as $method) {
echo '<div>';
echo '<h4<a id="method_'.$method['@name'].'">'.$method['@name'].'</a></h4>';
print_table($method);
echo '</div>';
}
}
/**
* Print a list hierarchically
*
* @param array $list
* @return void
*/
function print_list($list) {
echo '<ul>';
foreach ($list as $key => $value) {
if (is_array($value)) {
echo '<li>'.$key.print_list($value).'</li>';
} else {
echo '<li>'.$value.'</li>';
}
}
echo '</ul>';
}
/**
* Print an associative array as an HTML table.
*
* @param array[string => string] $data
* @param string $caption
* @return void
*/
function print_table($data, $caption = '') {
static $id = 0;
$id++;
$id_key = 't_key_'.$id;
$id_value = 't_value_'.$id;
if (!isset($data['description'])) {
$data['description'] = '[No description]';
}
echo '<table>';
if ($caption) {
echo '<caption>'.$caption.'</caption>';
}
echo '<thead>';
echo ' <tr>';
echo ' <th id="'.$id_key.'" scope="col">Key</th>';
echo ' <th id="'.$id_value.'" scope="col">Value</th>';
echo ' </tr>';
echo '</thead>';
echo '<tbody>';
$id_line = 't_desc_'.$id;
echo ' <tr>';
echo ' <th id="'.$id_line.'" scope="row" headers="'.$id_key.'">Description</th>';
echo ' <td headers="'.$id_value.' '.$id_line.'">'.nl2br($data['description']).'</td>';
echo ' </tr>';
foreach ($data as $key => $value) {
if ($key[0] == '@') {
$key = substr($key, 1);
list($key, $value) = convert_key_value($key, $value);
$id_line = 't_'.strtolower($key).'_'.$id;
echo ' <tr>';
echo ' <th id="'.$id_line.'" scope="row" headers="'.$id_key.'">'.ucfirst($key).'</th>';
echo ' <td headers="'.$id_value.' '.$id_line.'">'.$value.'</td>';
echo ' </tr>';
}
}
echo '</tbody>';
echo '</table>';
}
/**
* Convert a key value to be shown.
*
* @param string $key
* @param mixed $value
* @return array
*/
function convert_key_value($key, $value) {
if (is_array($value)) {
if (!count($value)) {
return array($key, '[empty]');
}
$first_key = current(array_keys($value));
$list_type = is_int($first_key) ? 'ol' : 'ul';
$return = '<'.$list_type.'>';
foreach ($value as $k => $v) {
list($k, $v) = convert_key_value($k, $v);
$return .= '<li>'.($list_type == 'ul' ? $k.': ' : '').$v.'</li>';
}
$return .= '</'.$list_type.'>';
return array($key, $return);
}
switch ($key) {
case 'license':
case 'see':
if (preg_match('/^(http[s]?:\/\/[^\040]+) (.+)$/', $value, $match)) {
$value = '<a href="'.$match[1].'">'.convert_value($match[2]).'</a>';
} else {
$value = convert_value($value);
}
break;
default:
$value = convert_value($value);
break;
}
return array($key, $value);
}
/**
* Convert a value to be shown.
*
* @param mixed $original
* @return string
*/
function convert_value($original) {
static $tr = array('(C)' => '©',
'(c)' => '©',
'(R)' => '®',
'(r)' => '®',
'(TM)' => '™',
'(tm)' => '™');
if (is_string($original)) {
return nl2br(strtr(utf8_encode($original), $tr));
} elseif (is_int($original)) {
return number_format($original, 0, '.', '');
} elseif (is_double($original)) {
return strval($original);
} elseif (is_bool($original)) {
return $original ? 'Yes' : 'No';
} elseif (is_null($original)) {
return '[empty]';
} else {
return '[value not printable]';
}
}
/**
* Convert a PHP value to a human readeable format.
*
* @param mixed $original
* @return string
*/
function convert_php_value($original) {
if (is_string($original)) {
return "'".$original."'";
} elseif (is_int($original)) {
return number_format($original, 0, '.', '');
} elseif (is_double($original)) {
return strval($original);
} elseif (is_bool($original)) {
return $original ? 'true' : 'false';
} elseif (is_null($original)) {
return 'null';
} elseif (is_array($original)) {
return '[array of size '.count($original).']';
} elseif (is_object($original)) {
return '[object of type '.get_class($original).']';
} elseif (is_resource($original)) {
return '[resource of type '.get_resource_type($original).']';
} else {
return '[undefined value]';
}
}
/**
* Parse a documentation comment and fill an associative array.
*
* @param string $comment
* @return array[string => string]
*/
function parse_comment($comment) {
$lines = explode("\n", $comment);
array_shift($lines);
$data = array('description' => '');
foreach ($lines as $line) {
$line = substr(trim($line), 2);
if ($line[0] == '@') {
$pos_space = strpos($line, ' ');
$attribute = substr($line, 0, $pos_space);
$value = substr($line, $pos_space + 1);
if (isset($data[$attribute])) {
if (is_array($data[$attribute])) {
$data[$attribute][] = $value;
} else {
$old_value = $data[$attribute];
$data[$attribute] = array($old_value, $value);
}
} else {
$data[$attribute] = $value;
}
unset($pos_space, $attribute, $value);
} else {
$data['description'] .= $line."\n";
}
}
$data['description'] = trim($data['description']);
return $data;
}
/**
* Get category comments and its line.
*
* @param string $filename
* @return array[int => string]
*/
function get_category_comments($filename) {
$lines = file($filename);
$comments = array();
foreach ($lines as $i => $line) {
if (strpos($line, '///') === 0) {
$comments[$i] = trim(substr($line, 3));
}
}
return $comments;
}
/**
* Get category from an element of specific line.
*
* @param array $comments category comments
* @param int $line line of element
* @return string
*/
function get_category(&$comments, $line) {
$last = '';
foreach ($comments as $l => $c) {
if ($l > $line) {
return $last;
}
$last = $c;
}
return $last;
}
/**
* Get all classes/interfaces in current directory.
*
* @param bool $complete true returns "type classname" / false returns "classname"
* @return array[string]
*/
function get_classes($complete = true) {
$dirname = dirname(__FILE__).'/';
$dir = scandir($dirname);
$classes = array();
$reg = '/^([a-z_]*)\.(class|interface)(\.php)$/';
foreach ($dir as $file) {
if (is_file($dirname.$file) && preg_match($reg, $file, $match)) {
if ($complete) {
$classes[$match[1]] = $match[2].' '.$match[1];
} else {
$classes[$match[1]] = $match[1];
}
}
}
asort($classes);
return $classes;
}
|