<?php // encoding="UTF-8"
/**

PHP documentation

© 2010, École nationale des chartes, licence CeCILL-C (LGPL compatible droit français)

A documentor working with the default PHP parser.

  • For efficiency, no echo or print, but write to streams (output, memory, or user defined).
  • Code enhanced to be read, but still working after copy/paste, no generated content like line numbers or labels
  • Possible cache on file timestamp
  • Working not too badly on other C like syntax files (css, javascript)
  // direct output
  PhpDoc::html('PhpDoc.php');
  // get result as a string
  $html=PhpDoc::html('PhpDoc.php', "");
  // output to a file, if the filepath provided is not writable, a path to a tmp file is returned.
  // If dest_file is newer than the source file, nothing is done.
  $dest_file=PhpDoc::html('PhpDoc.php', "PhpDoc.html");
  include($dest_file);
  // Give code as a string
  PhpDoc::html(file_get_contents('PhpDoc.php'));
  // Document css or js
  PhpDoc::html('theme.css', 'theme.html');
  PhpDoc::html(file_get_contents('script.js'), "", "js");

Inspired by Krijn Hoetmer.

*/
class PhpDoc { static $defined_functions; static $defined_constants;
/** Get html documentation from code in C style. @param $code filepath or code as a String @param $stream default or null : output html to screen filepath : output html to file "" : output html as a string @return filepath or html */
static function html($code, $stream=null, $type=null) { // input if (strlen($code) < 150 && file_exists($code)) { $src_file=$code; $code=file_get_contents($src_file); if (!$type) $type = substr($src_file, strrpos($src_file, '.') + 1); } // use the php parser for other kind of files if ($type && $type!="php") $code="<?php ".$code." ?>"; // output stream is OK if (is_resource($stream)); // defaut output else if($stream === null) $stream=fopen('php://output', "w"); // empty string, return as a string else if($stream === "") { $stream=fopen('php://memory', "r+"); $html=true; } // a string provided, supposed a filepath else { if ($stream && !file_exists(dirname($stream))) mkdir(dirname($stream), 0775, true); $stream=fopen($stream, "w"); if (isset($src_file)) $title=basename($src_file); else $title="PHP source"; fwrite($stream, '<html> <head> <meta http-equiv="Content-type" content="text/html; charset=UTF-8"/> <title>'.$title.'</title> <link rel="stylesheet" type="text/css" href="http://subversion.cru.fr/diple/trunk/xrem/xrem.css"/> </head> <body> '); } // if php, register namespace, TODO, eval php without output self::$defined_functions = get_defined_functions(); self::$defined_constants = get_defined_constants(); // Do the real job self::tokenize($code, $stream, $type); // return html, from the memory stream if (isset($html)) { rewind($stream); $html=stream_get_contents($stream); fclose($stream); return $html; } // return a file path (in case of tmp file) if(isset($dest_file)) { fwrite($stream, ' </body> </html> '); fclose($stream); return $dest_file; } }
/** Tokenize a php string to the stream provided @param $php php code as a String @param $stream a valid open stream */
static function tokenize($code, $stream, $type=null) { if (!$type) $type="php"; $construct=null; // array of tokens to loop on $tokens = @token_get_all($code); //keep @, may generate warnings foreach ($tokens as $tok) { // ponctuation if (!is_array($tok)) { $class= ($tok == '[' || $tok == ']')?"var": ($tok == '(' || $tok == ')')?"expr": ($tok == '{' || $tok == '}')?"block": ($tok == ',')?"enum": ($tok == ';')?"stmt": "punct"; fwrite($stream, '<tt class="'.$class.'">' . $tok . '</tt>'); continue; } $name = $tok[0]; $value = htmlspecialchars($tok[1], ENT_NOQUOTES, "UTF-8"); // get back the &lt; &gt; switch ($name) { case T_OPEN_TAG: case T_OPEN_TAG_WITH_ECHO: fwrite($stream, '<pre class="php">'); if ($type == "php") fwrite($stream, '<code class="php-tag">' . $value . '</code>'); break; case T_CLOSE_TAG: if ($type == "php") fwrite($stream, '<code class="php-tag">' . $value . '</code></pre>'); break; case T_STRING: if (in_array($value, self::$defined_functions['internal'])) { fwrite($stream, '<a class="function" href="http://www.php.net/' . $value . '" title="PHP : ' . $value . '">' . $value . '</a>'); } elseif (array_key_exists($value, self::$defined_constants)) { fwrite($stream, '<code class="constant" title="Constante : ' . self::$defined_constants[$value] . '">' . $value . '</code>'); } elseif ($construct=='function') { $construct=null; fwrite($stream, '<span class="function" id="'.$value.'">' . $value . '</span>'); } else { fwrite($stream, '<span class="function">' . $value . '</span>'); } break; case T_NUM_STRING: fwrite($stream, '<code class="numeric string">' . str_replace('<', '&lt;', str_replace('>', '&gt;', $value)) . '</code>'); break; case T_LINE: case T_FILE: case T_FUNC_C: case T_CLASS_C: fwrite($stream, '<code class="constant">' . $value . '</code>'); break; case T_INLINE_HTML: fwrite($stream, '<code class="html">' . str_replace('<', '&lt;', str_replace('>', '&gt;', $value)) . '</code>'); break; case T_LNUMBER: case T_DNUMBER: fwrite($stream, '<code class="integer">' . $value . '</code>'); break; case T_CONSTANT_ENCAPSED_STRING: fwrite($stream, '<code class="string">' . str_replace('<', '&lt;', str_replace('>', '&gt;', $value)) . '</code>'); break; case T_OBJECT_OPERATOR: case T_PAAMAYIM_NEKUDOTAYIM: fwrite($stream, '<code class="object operator">' . $value . '</code>'); break; case T_ECHO: case T_PRINT: case T_IF: case T_ELSEIF: case T_INCLUDE: case T_INCLUDE_ONCE: case T_REQUIRE: case T_REQUIRE_ONCE: case T_ELSE: case T_FOR: case T_SWITCH: case T_WHILE: case T_RETURN: case T_ISSET: case T_UNSET: case T_EMPTY: case T_ARRAY: case T_DO: case T_DECLARE: case T_CONTINUE: case T_NEW: case T_CASE: case T_FOREACH: case T_CONST: case T_EXIT: case T_DEFAULT: case T_AS: case T_BREAK: case T_FUNCTION: case T_EXTENDS: case T_VAR: case T_CLASS: $construct=$value; fwrite($stream, '<b class="construct">' . $value . '</b>'); break; case T_AND_EQUAL: case T_CONCAT_EQUAL: case T_DIV_EQUAL: case T_MINUS_EQUAL: case T_MOD_EQUAL: case T_MUL_EQUAL: case T_OR_EQUAL: case T_PLUS_EQUAL: case T_DOUBLE_ARROW: case T_SL_EQUAL: case T_INC: case T_DEC: case T_SR_EQUAL: case T_XOR_EQUAL: fwrite($stream, $value ); break; case T_SL: case T_SR: fwrite($stream, '<code class="bitwise operator">' . $value . '</code>'); break; case T_BOOLEAN_AND: case T_BOOLEAN_OR: case T_LOGICAL_AND: case T_LOGICAL_OR: case T_LOGICAL_XOR: fwrite($stream, '<code class="logical operator">' . $value . '</code>'); break; case T_IS_EQUAL: case T_IS_GREATER_OR_EQUAL: case T_IS_IDENTICAL: case T_IS_NOT_EQUAL: case T_IS_NOT_IDENTICAL: case T_IS_SMALLER_OR_EQUAL: fwrite($stream, '<code class="comparison operator">' . $value . '</code>'); break; case T_DOC_COMMENT: // a simple line comment if(strpos($tok[1], "\n")===false) { fwrite($stream, '<span class="comment">' . $value . '</span>'); break; } $pre=""; if (strrpos($tok[1], "<h") === FALSE && strrpos($tok[1], "<p") === FALSE) $pre=" pre"; // seems to contain html else { $value=$tok[1]; if (strpos($value, "<pre")) { $esc=false; // escape sample code in pre $lines=preg_split('/(<\/?pre[^>]*>)/', $value, -1, PREG_SPLIT_DELIM_CAPTURE); $value=""; foreach ($lines as $line) { if (strpos($line, "</pre>") === 0) $esc=false; if ($esc) $value .= htmlspecialchars($line, ENT_NOQUOTES, "UTF-8"); else $value.=$line; if (strpos($line, "<pre") === 0) $esc=true; } } } $value=preg_replace("/\n\s*\*(?=[ \n])/","\n",strtr($value, array("\t"=>" "))); fwrite($stream, '<fieldset class="comment'.$pre.'">' . $value . '</fieldset>'); break; case T_COMMENT: if (substr($tok[1],0, 2) == "//") fwrite($stream, '<span class="comment">' . $value . '</span>'); // attention au <> // hide simple multiline comments in php file else if ($type=="php" && substr($tok[1],0, 2) == "/*"); else fwrite($stream, '<span class="comment">' . $tok[1] . '</span>'); break; case T_VARIABLE: fwrite($stream, '<var>' . $value . '</var>'); break; case T_WHITESPACE: fwrite($stream, strtr($value, array("\t"=>" "))); // replace tabs, too large in <pre> break; default: fwrite($stream, $value); } // end case } // end loop } } // file is include do nothing if (realpath($_SERVER['SCRIPT_FILENAME']) != realpath(__FILE__)); // command line else if (php_sapi_name() == "cli") ; // appel direct else { $file=$_REQUEST['file']; echo '<style type="text/css"> pre.php { white-space: pre-wrap; font-size:14px; color:red; } .pre { white-space: pre-wrap; } code.function { color:#A82C37; } span.function { font-family:Frutiger,"Deja Vu Sans",Helvetica,sans-serif; color:#000; } a.function { font-family:Frutiger,"Deja Vu Sans",Helvetica,sans-serif; } a.function:hover { text-decoration:underline; } pre.php b.construct { font-family:Frutiger,"Deja Vu Sans",Helvetica,sans-serif; color:#000; } pre.php var { font-family:Frutiger,"Deja Vu Sans",Helvetica,sans-serif; color:#000000; font-style:normal; } code.string { color:#060; } /* comments */ .comment { font-family:sans-serif; color:#000; } /* line comments, choice si make them readable the pore as possible */ span.comment { color:#888; font-style:normal; } /* sample code in a comment */ fieldset.comment pre { color:#008; } div.comment { white-space: normal; } code.integer { color:#FF0000; font-weight:bold; } code.php-tag { color:#FF0000; } code.numeric { color:#FF0000; font-weight:bold; } code.constant { color:#000000; } tt.op { background:none repeat scroll 0 0 #EEEEEE; color:#000000; } code.defined { color:red; } code.whitespace { white-space:pre; } code.curly { background:none repeat scroll 0 0 #FFAAAA; } code.parenthese { background:none repeat scroll 0 0 #AAFFAA; } code.square { background:none repeat scroll 0 0 #AAAAFF; } code.error-control { background:none repeat scroll 0 0 #FF0000; font-weight:bold; } </style> '; PhpDoc::html('../../'.$file); }