<?php
/*  
  +----------------------------------------------------------------------+
  | PHP Version 4                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2004 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.0 of the PHP license,       |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_0.txt.                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Authors:    Hartmut Holzgraefe <hholzgra@php.net>                    |
  |             Gabor Hojtsy <goba@php.net>                              |
  +----------------------------------------------------------------------+
  
  $Id: file-entities.php.in,v 1.43 2006/03/17 15:10:18 vrana Exp $
*/

/**
 *
 * Create phpdoc/entities/file-entities.ent with respect
 * to all the specialities needed:
 *
 *  . CHM only appendix integration
 *  . Special install part (temporary)
 *  . Reserved constant part (temporary)
 *  . Translated language files with English ones as fallbacks
 *  . Global function index
 *  . additional extension documentation from PECL
 *  . additional extension documentation from standalone extensions
 *
 * Also take in account, that if XSLT style sheets are used,
 * special file:/// prefixed path values are needed.
 *
 */


// Always flush output
ob_implicit_flush();
// This script runs for a long time
set_time_limit(0);

// ......:ARGUMENT PARSING:.....................................................

$not_windows = !eregi('WIN',PHP_OS);

// The dir for PHP. If the cygwin wasn't compiled on Cygwin, the path needs to be stripped.
$out_dir = ($not_windows || eregi('CYGWIN',php_uname()))? "@WORKDIR@" : abs_path(strip_cygdrive("@WORKDIR@"));

// The language encoding to use
$encoding = "@ENCODING@";

// PHP, PECL and additional extension source directories if given
$php_source  = ("@PHP_SOURCE@"  === "no" ? false : abs_path("@PHP_SOURCE@"));
$pecl_source = ("@PECL_SOURCE@" === "no" ? false : abs_path("@PECL_SOURCE@"));
$ext_sources = array();
foreach (explode(":", "@EXT_SOURCE@") as $path) {
    if(!empty($path)) {
        $ext_sources[]= abs_path($path);
    }
}

// ......:ENTITY CREATION:......................................................

// Put all the file entities into $entities
$entities = array();
file_entities("$out_dir/en", "$out_dir/@LANGDIR@", "$out_dir/en", $entities);

$refdir = "$out_dir/en/reference";
$dh = opendir($refdir);
while ($dir = readdir($dh)) {
	if ($dir[0] === ".") continue; // ignore system dir entries and hidden files
	if (is_file("$refdir/$dir/reference.xml")
		&& !is_file("$refdir/$dir/EXCLUDE")) {
		$dirent = str_replace("_","-",$dir);
		$title = get_title("$refdir/$dir/reference.xml");
		if (!$title) $title = $dirent; // extension name is close enough
		$builtin_extensions[$title] = "&reference.$dirent.reference;";
	}
}
ksort($builtin_extensions);

create_part_file("entities/builtin-extensions.xml", $builtin_extensions, 'funcref', '&FunctionReference;', '<para>See also <xref linkend="extensions"/>.</para>');
$entities[] = entstr("builtin.extensions", "$out_dir/entities/builtin-extensions.xml");

// additional documentation embedded in PECL extensions
$pecl_extensions = array();

if($pecl_source) {
    $dh = opendir($pecl_source);

    $pecl_dirs = array();
    while($dir = readdir()) { 
        if($dir[0] === ".") continue; // ignore system dir entries and hidden files
        $pecl_dirs[] = $dir;
    }
    
    foreach ($pecl_dirs as $dir) {
        $docdir = "$pecl_source/$dir/manual";

        if (@is_dir($docdir)) {

            $dirent = str_replace("_","-",$dir);          
            if(@is_dir("$docdir/en")) {
                file_entities("$docdir/en", "$docdir/@LANGDIR@", "$docdir/en", $entities, "reference.$dirent");
            } else if (@is_dir("$docdir/$dir")) {
                file_entities("$docdir/$dir", "$docdir/$dir", "$docdir/$dir", $entities, "reference.$dirent");          
            } else if (@is_dir("$docdir/$dirent")) {
                file_entities("$docdir/$dirent", "$docdir/$dirent", "$docdir/$dirent", $entities, "reference.$dirent");
            } else {
                continue;
            }
            $pecl_extensions[] = "&reference.$dirent.reference;";
	    sort($pecl_extensions);
        }
    }
    closedir($dh);
}

create_part_file("entities/pecl-extensions.xml", $pecl_extensions, 'pecl-funcref', "&PECLReference;");
$entities[] = entstr("pecl.extensions", "$out_dir/entities/pecl-extensions.xml");

// additional documentation embedded in external extension sources
$addon_extensions = array();
foreach ($ext_sources as $ext_dir) {
    $docdir = "$ext_dir/manual";
    $dir = basename($ext_dir);
    $dirent = str_replace("_","-",$dir);
    if(@is_dir("$docdir/en/reference/$dir")) {
        file_entities("$docdir/en/reference/$dir", "$docdir/@LANGDIR@/reference/$dir", "$docdir/en/reference/$dir", $entities, "reference.$dirent");
    } else if (@is_dir("$docdir/$dir")) {
        file_entities("$docdir/$dir", "$docdir/$dir", "$docdir/$dir", $entities, "reference.$dirent");          
    } else if (@is_dir("$docdir/$dirent")) {
        file_entities("$docdir/$dirent", "$docdir/$dirent", "$docdir/$dirent", $entities, "reference.$dirent");         
    } else {
        continue;
    }
    $addon_extensions[] = "&reference.$dirent.reference;";
    $sort($addon_extensions);
}

create_part_file("entities/addon-extensions.xml", $addon_extensions, 'addon-funcref', '&AddOnReference;');
$entities[] = entstr("addon.extensions", "$out_dir/entities/addon-extensions.xml");


// Open file for appending and write out all entitities
$fp = fopen("$out_dir/entities/file-entities.ent", "w");
if (!$fp) {
    die("ERROR: Failed to open $out_dir/entities/file-entities.ent for writing\n");
}

echo "\nCreating file: entities/file-entities.ent...\n";

// File header
fputs($fp, "<!-- DON'T TOUCH - AUTOGENERATED BY file-entities.php -->\n\n");

// The global function index page is special
fputs(
   $fp,
   "<!-- global function index file -->\n" .
   entstr("global.function-index", "$out_dir/funcindex.xml") . "\n"
);

// The missing id file is generated after this pass
fputs(
   $fp,
   "<!-- generated file with missing ids -->\n" .
   entstr("missing-ids", "$out_dir/entities/missing-ids.xml") . "\n" .
   "<!-- all other files -->\n"
);

// Write out all other entities
foreach ($entities as $entity) {
    fputs($fp, $entity);
}
fclose($fp);

// Here is the end of the code
exit;

// ......:FUNCTION DECLARATIONS:................................................

/**
 * Generate absolute path from a relative path, taking accout
 * the current wokring directory.
 *
 * @param string $path Relative path
 * @return string Absolute path generated
 */
function abs_path($path) {

    // This is already an absolute path (begins with / or a drive letter)
    if (preg_match("!^(/|\\w:)!", $path)) { return $path; }

    // Get directory parts

    $absdir  = str_replace("\\", "/", getcwd());
    $absdirs = explode("/", preg_replace("!/scripts$!", "", $absdir));
    $dirs    = explode("/", $path);

    // Generate array representation of absolute path
    foreach ($dirs as $dir) {
        if (empty($dir) or $dir == ".") continue;
        else if ($dir == "..") array_pop($absdirs);
        else array_push($absdirs, $dir);
    }

    // Return with string
    return join("/", $absdirs);
}

/**
 * Create file entities, and put them into the array passed as the
 * last argument (passed by reference).
 *
 * @param string $work_dir English files' directory
 * @param string $trans_dir Translation's directory
 * @param string $orig_dir Original directory
 * @param array $entities Entities string array
 * @return boolean Success signal
 */
function file_entities($work_dir, $trans_dir, $orig_dir, &$entities, $prefix=false) {

    // Compute translated version's path
    $trans_path = str_replace($orig_dir, $trans_dir, $work_dir);
    
    // Try to open English working directory
    $dh = opendir($work_dir);
    if (!$dh) { 
        return FALSE; 
    }

    // If the working directory is a reference functions directory,
    // then start to generate a functions.xml file for that folder.
    if (preg_match("!/functions$!", $work_dir) || preg_match("!/macros$!", $work_dir)) {
        // Start new functions file with empty entity set
        $function_entities = array();
        $functions_file = "$work_dir.xml";

        // Get relative file path to original directory, and form an entity
        $functions_file_entity = str_replace("$orig_dir/", "", $work_dir);
        $functions_file_entity = fname2entname($functions_file_entity, $prefix);
        $entities[] = entstr($functions_file_entity, $functions_file);
    }

    // While we can read that directory
    while (($file = readdir($dh)) !== FALSE) {
        // If file name begins with . skip it
        if ($file[0] == ".") { continue; }

        // If we found a directory, and it's name is not
        // CVS, recursively go into it, and generate entities
        if (is_dir($work_dir . "/" . $file)) {
            if ($file == "CVS") { continue; }
            file_entities($work_dir . "/" . $file, $trans_dir, $orig_dir, $entities, $prefix);
        }

        // If the file name ends in ".xml"
        if (preg_match("!\\.xml$!", $file)) {
            
            // Get relative file name and get entity name for it
            $name = str_replace(
                "$orig_dir/",
                "",
                $work_dir . "/" . preg_replace("!\\.xml$!", "", $file)
            );
            $name = fname2entname($name, $prefix);

            // If this is a functions directory, collect it into
            // the special $function_entities array
            if (isset($function_entities)) {
                $function_entities[] = "&$name;";
            }
            
            // If we have a translated file, use it, otherwise fall back to English
            if (file_exists("$trans_path/$file")) {
                $path = "$trans_path/$file";
            } else {
                $path = "$work_dir/$file";
            }

            // Append to entities array
            $entities[] = entstr($name, $path);

        } // end of "if xml file"
    } // end of readdir loop
    
    // Close directory
    closedir($dh);

    // If we created a function entities list, write it out
    if (isset($function_entities)) {
        
        // Sort by name
        sort($function_entities);
        
        // Write out all entities with newlines
        $fp = fopen($functions_file, "w");
        foreach ($function_entities as $entity) {
            fputs($fp, "$entity\n");
        }
        fclose($fp);
    }

    // Find all files available in the translation but not in the original English tree
    if ($orig_dir != $trans_dir && file_exists($trans_path) && is_dir($trans_path)) {

        // Open translation path
        $dh = @opendir($trans_path);

        if ($dh) {

            while (($file = readdir($dh)) !== FALSE) {
                if ($file[0] =="." || $file == "CVS") { continue; }
                if (is_dir($trans_path."/".$file)) { continue; }
                
                // If this is an XML file
                if (preg_match("!\\.xml$!",$file)) {
                    
                    // Generate relative file path and entity name out of it
                    $name = str_replace(
                        "$orig_dir/",
                        "",
                        $work_dir . "/" . preg_replace("!\\.xml$!", "", $file)
                    );
                    $name = fname2entname($name, $prefix);
                    
                    // If the file found is not in the English dir, append to entities list
                    if (!file_exists("$work_dir/$file")) {
                        $path = "$trans_path/$file";
                        $entities[] = entstr($name, $path);
                    }

                } // if this is an XML file end

            } // readdir iteration end
            closedir($dh);
        }
    }
    
} // end of funciton file_entities()

/**
 * Convert a file name (with path) to an entity name.
 *
 * Converts: _ => - and / => .
 *
 * @param string $fname File name
 * @return string Entity name
 */
function fname2entname($fname, $prefix=false)
{
    $ent = str_replace("_", "-", str_replace("/", ".", $fname));
	if ($prefix && !strstr($ent, $prefix)) {
		$ent = "$prefix.$ent";
	}
	return $ent;
}

/**
 * Return entity string with the given entityname and filename.
 * 
 * @param string $entname Entity name
 * @param string $filename Name of file
 * @return string Entity declaration string
 */
function entstr($entname, $filename)
{
    // If we have no file, than this is not a system entity
    if ($filename == "") {
        return sprintf("<!ENTITY %-40s        ''>\n", $entname);
    } else {
        return sprintf("<!ENTITY %-40s SYSTEM '%s'>\n", $entname, file2jade($filename));
    }
}

/**
 * Return windows style path for cygwin.
 * 
 * @param string $path Orginal path
 * @return string windows style path
 */
function strip_cygdrive($path){
    return preg_replace(array('!^/cygdrive/(\w)/!', '@^/home/.+$@'), array('\1:/', strtr(dirname(dirname(__FILE__)), '\\', '/')), $path);
}


/* Converts a path to the appropriate style for Jade */
function file2jade($path) {

    if ($GLOBALS['not_windows'])
	return $path;

    if ((bool)@WINJADE@) {
        return strip_cygdrive($path);
    } else {
        return preg_replace('@^([a-zA-Z]):/@S', '/cygdrive/$1/', $path);
    }
}

/** 
 * Create a reference <part>
 *
 * @param string Name of file to create (relative to phpdoc dir)
 * @param array  Entities to list in this file
 * @param string XML Id for the files top level <part> tag
 * @param string Title for this <part>
 * @param string Contents of <partintro>
 */
function create_part_file ($filename, $entities, $id, $title, $partintro = "")
{
	global $out_dir, $encoding;

	$fp = fopen("$out_dir/$filename","w");
	fputs($fp, "<?xml version='1.0' encoding='$encoding' ?>\n");
	fputs($fp, "<!-- FILE AUTOGENERATED BY file-entities.php, DO NOT EDIT !!! -->\n");
	if(count($entities)) {
		fputs($fp, "<part id='$id'>\n");
		fputs($fp, " <title>$title</title>\n");
		if ($partintro) {
			fputs($fp, " <partintro>\n  $partintro\n </partintro>\n");
		}

		foreach ($entities as $entity) {
			fputs($fp, "   $entity\n");
		}
		fputs($fp,"</part>\n");
	} else {
		fputs($fp, "<!-- File intentionally left blank -->\n");
	}
	fclose($fp);
}

/**
 * Pull the main title from reference.xml for sorting the table of contents.
 * To avoid bogus sorting, trim whitespace and convert the string to upper case.
 *
 * @param filename Path to reference.xml (relative to phpdoc dir)
 */
function get_title ($filename)
{
	$matches = array();

	$contents = file_get_contents($filename);

	/*
	 * Very basic approach for getting the title. We really should:
	 * - strip all comments (<!-- --> and <![CDATA[ ]]>) before
	 *   searching for <title>
	 * - strip tags from inside <title> ... </title>
	 * - resolve character entities
	 *
	 * ... but this is probably good enough for 98.5% of extensions
	 */

	if (preg_match("#titleabbrev>(.*?)</titleabbrev>#", $contents, $matches)) {
		return strtoupper(trim($matches[1]));
	}

	if (preg_match("#<title>(.*?)</title>#", $contents, $matches)) {
		return strtoupper(trim($matches[1]));
	}

	return null;
}

?>
