<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of Plume Framework, a simple PHP Application Framework.
# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
#
# Plume Framework is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Plume Framework is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Class to extract the translation strings from the template.
*
* Based on Pluf_Template_Compiler with code:
* Copyright (C) 2006 Laurent Jouanneau.
*/
class Pluf_Translation_TemplateExtractor
{
/**
* Store the literal blocks.
**/
protected $_literals;
/**
* Variables.
*/
protected $_vartype = array(T_CHARACTER, T_CONSTANT_ENCAPSED_STRING,
T_DNUMBER, T_ENCAPSED_AND_WHITESPACE,
T_LNUMBER, T_OBJECT_OPERATOR, T_STRING,
T_WHITESPACE, T_ARRAY);
/**
* Assignation operators.
*/
protected $_assignOp = array(T_AND_EQUAL, T_DIV_EQUAL, T_MINUS_EQUAL,
T_MOD_EQUAL, T_MUL_EQUAL, T_OR_EQUAL,
T_PLUS_EQUAL, T_PLUS_EQUAL, T_SL_EQUAL,
T_SR_EQUAL, T_XOR_EQUAL);
/**
* Operators.
*/
protected $_op = array(T_BOOLEAN_AND, T_BOOLEAN_OR, T_EMPTY, T_INC,
T_ISSET, T_IS_EQUAL, T_IS_GREATER_OR_EQUAL,
T_IS_IDENTICAL, T_IS_NOT_EQUAL, T_IS_NOT_IDENTICAL,
T_IS_SMALLER_OR_EQUAL, T_LOGICAL_AND, T_LOGICAL_OR,
T_LOGICAL_XOR, T_SR, T_SL, T_DOUBLE_ARROW);
/**
* Authorized elements in variables.
*/
protected $_allowedInVar;
/**
* Authorized elements in expression.
*/
protected $_allowedInExpr;
/**
* Authorized elements in assignation.
*/
protected $_allowedAssign;
/**
* Output filters.
*/
protected $_modifier = array('upper' => 'strtoupper',
'lower' => 'strtolower',
'escxml' => 'htmlspecialchars',
'escape' => 'Pluf_Template_htmlspecialchars',
'strip_tags' => 'strip_tags',
'escurl' => 'rawurlencode',
'capitalize' => 'ucwords',
// Not var_export because of recursive issues.
'debug' => 'print_r',
'fulldebug' => 'var_export',
'count' => 'count',
'nl2br' => 'nl2br',
'trim' => 'trim',
'unsafe' => 'Pluf_Template_unsafe',
'safe' => 'Pluf_Template_unsafe',
'date' => 'Pluf_Template_dateFormat',
'time' => 'Pluf_Template_timeFormat',
);
/**
* After the compilation is completed, this contains the list of
* modifiers used in the template. The GetCompiledTemplate method
* will add a series of Pluf::loadFunction at the top to preload
* these modifiers.
*/
public $_usedModifiers = array();
/**
* Default allowed extra tags/functions.
*
* These default tags are merged with the 'template_tags' defined
* in the configuration of the application.
*/
protected $_allowedTags = array(
'url' => 'Pluf_Template_Tag_Url',
);
/**
* During compilation, all the tags are created once so to query
* their interface easily.
*/
protected $_extraTags = array();
/**
* The block stack to see if the blocks are correctly closed.
*/
protected $_blockStack = array();
/**
* Special stack for the translation handling in blocktrans.
*/
protected $_transStack = array();
protected $_transPlural = false;
/**
* Current template source file.
*/
protected $_sourceFile;
/**
* Current tag.
*/
protected $_currentTag;
/**
* Template folders.
*/
public $templateFolders = array();
/**
* Template content. It can be set directly from a string.
*/
public $templateContent = '';
/**
* Construct the compiler.
*
* @param string Basename of the template file.
* @param array Base folders in which the templates files
* should be found. (array())
* @param bool Load directly the template content. (true)
*/
function __construct($template_file, $folders=array(), $load=true)
{
$allowedtags = Pluf::f('template_tags', array());
$this->_allowedTags = array_merge($allowedtags, $this->_allowedTags);
$modifiers = Pluf::f('template_modifiers', array());
$this->_modifier = array_merge($modifiers, $this->_modifier);
foreach ($this->_allowedTags as $name=>$model) {
$this->_extraTags[$name] = new $model();
}
$this->_sourceFile = $template_file;
$this->_allowedInVar = array_merge($this->_vartype, $this->_op);
$this->_allowedInExpr = array_merge($this->_vartype, $this->_op);
$this->_allowedAssign = array_merge($this->_vartype, $this->_assignOp,
$this->_op);
$this->templateFolders = $folders;
if ($load) {
$this->loadTemplateFile($template_file);
}
}
/**
* Get blocktrans.
*/
function getBlockTrans()
{
$tplcontent = $this->templateContent;
$tplcontent = preg_replace('!{\*(.*?)\*}!s', '', $tplcontent);
$match = array();
preg_match_all('!{blocktrans(.*?){/blocktrans}!s', $tplcontent, $match);
$res = array();
foreach ($match[1] as $m) {
$res[] = '{blocktrans'.$m.'{/blocktrans}';
}
return $res;
}
/**
* Get simple trans call.
*/
function getSimpleTrans()
{
$tplcontent = $this->templateContent;
$tplcontent = preg_replace('!{\*(.*?)\*}!s', '', $tplcontent);
$match = array();
preg_match_all('!{trans(.*?)}!s', $tplcontent, $match);
$res = array();
foreach ($match[1] as $m) {
$res[] = '{trans'.$m.'}';
}
return $res;
}
/**
* Compile the template into a file ready to parse with xgettext.
*
* @return string PHP code of the compiled template.
*/
function compile()
{
$result = '';
$blocktrans = $this->getBlockTrans();
// Parse the blocktrans
foreach ($blocktrans as $block) {
$match = array();
if (preg_match('!{blocktrans(.*?)}(.*?){plural}(.*?){/blocktrans}!s', $block, $match)) {
$sing = $match[2];
$plural = $match[3];
$sing = preg_replace_callback('/{((.).*?)}/s',
array($this, '_callbackInTransBlock'),
$sing);
$plural = preg_replace_callback('/{((.).*?)}/s',
array($this, '_callbackInTransBlock'),
$plural);
$result .= '_n(\''.addcslashes($sing, "'").'\', \''.addcslashes($plural, "'").'\', $n);'."\n";
} elseif (preg_match('!{blocktrans}(.*?){/blocktrans}!s', $block, $match)) {
$sing = preg_replace_callback('/{((.).*?)}/s',
array($this, '_callbackInTransBlock'),
$match[1]);
$result .= '__(\''.addcslashes($sing, "'").'\');'."\n";
}
}
$simpletrans = $this->getSimpleTrans();
foreach ($simpletrans as $content) {
$result .= preg_replace_callback('/{((.).*?)}/s',
array($this, '_callback'),
$content);
}
return '<?php # This is not a valid php code. It is just for gettext use'."\n\n".$result.' ?>';
}
/**
* Load a template file.
*
* The path to the file to load is relative and the file is found
* in one of the $templateFolders array of folders.
*
* @param string Relative path of the file to load.
*/
function loadTemplateFile($file)
{
// FIXME: Very small security check, could be better.
if (strpos($file, '..') !== false) {
throw new Exception(sprintf(__('Template file contains invalid characters: %s'), $file));
}
foreach ($this->templateFolders as $folder) {
if (file_exists($folder.'/'.$file)) {
$this->templateContent = file_get_contents($folder.'/'.$file);
return;
}
}
// File not found in all the folders.
throw new Exception(sprintf(__('Template file not found: %s'), $file));
}
function _callback($matches)
{
list(,$tag, $firstcar) = $matches;
if ($firstcar != 't') {
trigger_error(sprintf(__('Invalid tag in translation extractor: %s'), $tag), E_USER_ERROR);
return '';
}
$this->_currentTag = $tag;
if (!preg_match('/^(\/?[a-zA-Z0-9_]+)(?:(?:\s+(.*))|(?:\((.*)\)))?$/', $tag, $m)) {
trigger_error(sprintf(__('Invalid function syntax: %s'), $tag), E_USER_ERROR);
return '';
}
if (count($m) == 4){
$m[2] = $m[3];
}
if (!isset($m[2])) $m[2] = '';
if ($m[1] == 'trans') {
return $this->_parseFunction($m[1], $m[2]);
}
return '';
}
function _callbackInTransBlock($matches)
{
list(,$tag, $firstcar) = $matches;
if (!preg_match('/^\$|[\'"]|[a-zA-Z\/]$/', $firstcar)) {
trigger_error(sprintf(__('Invalid tag syntax: %s'), $tag), E_USER_ERROR);
return '';
}
$this->_currentTag = $tag;
if ($firstcar == '$') {
$tok = explode('|', $tag);
$this->_transStack[substr($tok[0], 1)] = $this->_parseVariable($tag);
return '%%'.substr($tok[0], 1).'%%';
}
return '';
}
function _parseVariable($expr)
{
$tok = explode('|', $expr);
$res = $this->_parseFinal(array_shift($tok), $this->_allowedInVar);
// We do not take into account the modifiers.
return $res;
}
function _parseFunction($name, $args)
{
switch ($name) {
case 'trans':
$argfct = $this->_parseFinal($args, $this->_allowedAssign);
$res = '__('.$argfct.');'."\n";
break;
default:
$res = '';
break;
}
return $res;
}
/*
-------
if: op, autre, var
foreach: T_AS, T_DOUBLE_ARROW, T_VARIABLE, @locale@
for: autre, fin_instruction
while: op, autre, var
assign: T_VARIABLE puis assign puis autre, ponctuation, T_STRING
echo: T_VARIABLE/@locale@ puis autre + ponctuation
modificateur: serie de autre séparé par une virgule
tous : T_VARIABLE, @locale@
*/
function _parseFinal($string, $allowed=array(),
$exceptchar=array(';'))
{
$tokens = token_get_all('<?php '.$string.'?>');
$result = '';
$first = true;
$inDot = false;
$firstok = array_shift($tokens);
$afterAs = false;
$f_key = '';
$f_val = '';
$results = array();
// il y a un bug, parfois le premier token n'est pas T_OPEN_TAG...
if ($firstok == '<' && $tokens[0] == '?' && is_array($tokens[1])
&& $tokens[1][0] == T_STRING && $tokens[1][1] == 'php') {
array_shift($tokens);
array_shift($tokens);
}
foreach ($tokens as $tok) {
if (is_array($tok)) {
list($type, $str) = $tok;
$first = false;
if($type == T_CLOSE_TAG){
continue;
}
if ($type == T_AS) {
$afterAs = true;
}
if ($type == T_STRING && $inDot) {
$result .= $str;
} elseif ($type == T_VARIABLE) {
$result .= '$t->_vars[\''.substr($str, 1).'\']';
} elseif ($type == T_WHITESPACE || in_array($type, $allowed)) {
$result .= $str;
} else {
trigger_error(sprintf(__('Invalid syntax: (%s) %s.'), $this->_currentTag, $str), E_USER_ERROR);
return '';
}
} else {
if (in_array($tok, $exceptchar)) {
trigger_error(sprintf(__('Invalid character: (%s) %s.'), $this->_currentTag, $tok), E_USER_ERROR);
} elseif ($tok == '.') {
$inDot = true;
$result .= '->';
} elseif ($tok == '~') {
$result .= '.';
} elseif ($tok =='[') {
$result.=$tok;
} elseif ($tok ==']') {
$result.=$tok;
} elseif ($getAsArray && $tok == ',') {
$results[]=$result;
$result='';
} else {
$result .= $tok;
}
$first = false;
}
}
return $result;
}
}