#!/usr/pkg/bin/php -f
<?php
/*
 * PHP script to extract strings from all the files and print
 * to stdout for use with xgettext.
 *
 * This script is based on the perl script provided with the Horde project
 * http://www.horde.org/.  As such, it inherits the license from the
 * original version.  You can find that license here:
 *
 * http://cvs.horde.org/co.php/horde/COPYING?r=2.1
 *
 * I'm not exactly sure what the license restrictions are in this case,
 * but I want to give full credit to the original authors:
 *
 * Copyright 2000-2002 Joris Braakman <jbraakman@yahoo.com>
 * Copyright 2001-2002 Chuck Hagenbuch <chuck@horde.org>
 * Copyright 2001-2002 Jan Schneider <jan@horde.org>
 *
 * We've modified the script somewhat to make it work cleanly with the
 * way that Gallery embeds internationalized text, so let's tack on our
 * own copyrights.
 *
 * Copyright (C) 2002-2008 Bharat Mediratta <bharat@menalto.com>
 *
 * $Id: extract.php 17580 2008-04-13 00:38:13Z tnalmdal $
 */

if (!empty($_SERVER['SERVER_NAME'])) {
    errorExit("You must run this from the command line\n");
}
if (!function_exists('token_get_all')) {
    errorExit("PHP tokenizer required.\n"
	    . "Must use a PHP binary that is NOT built with --disable-tokenizer\n");
}

$exts = '(class|php|inc|css|html|tpl)';
$idEmitted = false;
$strings = array();
array_shift($_SERVER['argv']);
foreach ($_SERVER['argv'] as $moduleDir) {
    if (preg_match('#^/cygdrive/(\w+)/(.*)$#', trim($moduleDir), $matches)) {
	/* Cygwin and Window PHP filesystem function don't play nice together. */
	$moduleDir = $matches[1] . ':\\' . str_replace('/', '\\', $matches[2]);
    }
    if (!is_dir($moduleDir)) {
	continue;
    }
    chdir($moduleDir);
    find('.');

    $oldStringsRaw = "$moduleDir/po/strings.raw";
    if (file_exists($oldStringsRaw)) {
	$lines = file($oldStringsRaw);
	if (preg_match('/^#.*Id/', $lines[0])) {
	    print $lines[0];
	    $idEmitted = true;
	}
    }
}

if (!$idEmitted) {
    print '# $' . 'Id$' . "\n";
}
foreach ($strings as $string => $otherFiles) {
    print $string;
    if (!empty($otherFiles)) {
	print ' /* also in: ' . implode(' ', $otherFiles) . ' */';
    }
    print "\n";
}

/**
 * Recursive go through subdirectories
 */
function find($dir) {
    if ($dh = opendir($dir)) {
	$listing = $subdirs = array();
	while (($file = readdir($dh)) !== false) {
	    if ($file == '.' || $file == '..') {
		continue;
	    }
	    $listing[] = $file;
	}
	closedir($dh);
	sort($listing);
	global $exts;
	$dir = ($dir == '.') ? '' : ($dir . '/');
	foreach ($listing as $file) {
	    $filename = $dir . $file;
	    if (is_dir($filename)) {
		/* Don't parse unit tests */
		if ($file != 'test') {
		    $subdirs[] = $filename;
		}
	    } else if (preg_match('/\.' . $exts . '$/', $file)) {
		extractStrings($filename);
	    }
	}
	foreach ($subdirs as $dir) {
	    find($dir);
	}
    }
}

/**
 * Grab all translatable strings in a file into $strings array
 */
function extractStrings($filename) {
    global $strings;
    $strings["\n/* $filename */"] = array();
    $startSize = count($strings);
    $localStrings = array();
    $data = file_get_contents($filename);

    /*
     * class|inc|php are module and core PHP files.
     * Parse .html as PHP for installer/upgrader templates/*.html files.
     * Parse .css as PHP for modules/colorpack/packs/{name}/color.css files.
     */
    if (preg_match('/\.(class|inc|php|css|html)$/', $filename)) {
	/* Tokenize PHP code and process to find translate( or i18n( or _( calls */
	$tokens = token_get_all($data);
	for ($i = 0; $i < count($tokens); $i++) {
	    if (is_array($tokens[$i]) && $tokens[$i][0] == T_STRING
		    && in_array($tokens[$i][1], array('translate', '_translate', 'i18n', '_'))
		    && $tokens[$i + 1] === '(') {
		/* Found a function call for translation, process the contents */
		for ($i += 2; is_array($tokens[$i]) && $tokens[$i][0] == T_WHITESPACE; $i++) { }
		if (is_array($tokens[$i]) && $tokens[$i][0] == T_VARIABLE) {
		    /* Skip translate($variable) */
		    continue;
		}
		for ($buf = '', $parenCount = $ignore = 0; $i < count($tokens); $i++) {
		    /* Fill $buf with translation params; so end at , or ) not in a nested () */
		    if (!$parenCount && ($tokens[$i] === ')' || $tokens[$i] === ',')) {
			break;
		    }
		    if ($ignore && $parenCount == $ignore
			    && ($tokens[$i] === ',' || $tokens[$i] === ')')) {
			$ignore = false;
		    }
		    if ($tokens[$i] === '(') {
			$parenCount++;
		    } else if ($tokens[$i] === ')') {
			$parenCount--;
		    }
		    if (is_array($tokens[$i]) && $tokens[$i][0] == T_CONSTANT_ENCAPSED_STRING) {
			$lastString = $tokens[$i][1];
		    }
		    if (!$ignore) {
			$buf .= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i];
		    }
		    if (is_array($tokens[$i]) && $tokens[$i][0] == T_DOUBLE_ARROW
			    && (substr($lastString, 1, 3) === 'arg'
				|| substr($lastString, 1, 5) === 'count')) {
			/*
			 * Convert 'argN' => code to 'argN' => null so we don't eval that code.
			 * Add 'null' to $buf now, then ignore content until next , or ) not in
			 * a deeper nested ().
			 */
			$buf .= 'null';
			$ignore = $parenCount;
		    }
		}
		$param = eval('return ' . $buf . ';');
		if (is_string($param)) {
		    /* Escape double quotes and newlines */
		    $text = strtr($param, array('"' => '\"', "\r\n" => '\n', "\n" => '\n'));
		    $string = 'gettext("' . $text . '")';
		    if (!isset($strings[$string])) {
			$strings[$string] = array();
		    } else if (!isset($localStrings[$string])) {
			$strings[$string][] = $filename;
		    }
		    $localStrings[$string] = true;
		} else if (is_array($param)) {
		    foreach (array('text', 'one', 'many') as $key) {
			if (isset($param[$key])) {
			    /* Escape double quotes and newlines */
			    $param[$key] = strtr($param[$key],
						 array('"' => '\\"', "\r\n" => '\n', "\n" => '\n'));
			}
		    }
		    if (isset($param['one'])) {
			$string = 'ngettext("' . $param['one'] . '", "' . $param['many'] . '")';
		    } else {
			$string = 'gettext("' . $param['text'] . '")';
		    }
		    if (isset($param['cFormat'])) {
			$string = '/* xgettext:'
				. ($param['cFormat'] ? '' : 'no-') . "c-format */\n$string";
		    }
		    if (!empty($param['hint'])) {
			$string = '// HINT: ' . str_replace("\n", "\n// ", $param['hint'])
				. "\n$string";
		    }
		    if (!isset($strings[$string])) {
			$strings[$string] = array();
		    } else if (!isset($localStrings[$string])) {
			$strings[$string][] = $filename;
		    }
		    $localStrings[$string] = true;
		}
	    }
	}
    } else if (preg_match_all('/{\s*g->(?:text|changeInDescendents)\s+.*?[^\\\\]}/s',
			      $data, $matches)) {
	/* Use regexp to process tpl files for {g->text ..} and {g->changeInDescendents ..} */
	foreach ($matches[0] as $string) {
	    $text = $one = $many = null;

	    /*
	     * Ignore translations of the form:
	     *   text=$foo
	     * as we expect those to be variables containing values that
	     * have been marked elsewhere with the i18n() function.
	     */
	    if (preg_match('/\stext=\$/', $string)) {
		continue;
	    }

	    /* text=..... */
	    if (preg_match('/\stext="(.*?[^\\\\])"/s', $string, $matches)) {
		$text = $matches[1];
	    } else if (preg_match("/text='(.*?)'/s", $string, $matches)) {
		$text = str_replace('"', '\"', $matches[1]);    /* Escape double quotes */
	    }

	    /* one=..... */
	    if (preg_match('/\sone="(.*?[^\\\\])"/s', $string, $matches)) {
		$one = $matches[1];
	    } else if (preg_match("/\sone='(.*?)'/s", $string, $matches)) {
		$one = str_replace('"', '\"', $matches[1]);    /* Escape double quotes */
	    }

	    /* many=..... */
	    if (preg_match('/\smany="(.*?[^\\\\])"/s', $string, $matches)) {
		$many = $matches[1];
	    } else if (preg_match("/\smany='(.*?)'/s", $string, $matches)) {
		$many = str_replace('"', '\"', $matches[1]);    /* Escape double quotes */
	    }

	    /* Hint for translators */
	    $translatorHint = preg_match('/\shint=((["\']).*?[^\\\\]\2)/s', $string, $matches)
		? eval('return ' . $matches[1] . ';') : '';

	    /* c-format hint for xgettext */
	    $cFormatHint = preg_match('/\sc[Ff]ormat=(true|false)/s', $string, $matches)
		? '/* xgettext:' . ($matches[1] == 'false' ? 'no-' : '') . "c-format */\n" : '';

	    /* Pick gettext() or ngettext() and escape newlines */
	    if (isset($text)) {
		$string = 'gettext("' . strtr($text, array("\r\n" => '\n', "\n" => '\n')) . '")';
	    } else if (isset($one) && isset($many)) {
		$string = 'ngettext("' . strtr($one, array("\r\n" => '\n', "\n" => '\n')) . '", "'
			. strtr($many, array("\r\n" => '\n', "\n" => '\n')) . '")';
	    } else {
		/* Parse error */
		$string = str_replace("\n", '\n> ', $string);
		errorExit("extract.php parse error: $filename:\n> $string\n");
	    }

	    if ($cFormatHint) {
		$string = $cFormatHint . $string;
	    }
	    if ($translatorHint) {
		$string = "// HINT: $translatorHint\n$string";
	    }
	    if (!isset($strings[$string])) {
		$strings[$string] = array();
	    } else if (!isset($localStrings[$string])) {
		$strings[$string][] = $filename;
	    }
	    $localStrings[$string] = true;
	}
    }
    if (count($strings) == $startSize) {
	unset($strings["\n/* $filename */"]);
    }
}

function errorExit($message) {
    $stderr = fopen('php://stderr', 'w');
    fwrite($stderr, $message);
    exit(1);
}
?>
