| precede array:foo ( =<> - for {*array:foo_1>array:foo_2*}, | - for {*array:foo_1|array:foo_2*}, * - for just {*array:foo*} ) 0.1.24 - (beta) rewriting regexps with no named subpatterns for compatibility with old PHP versions (PCRE documentation is unclear and issues like new PHP and no support of named subpatterns occur) EXCEPT pattern for addvars method 0.1.23 - fixed ^KEY parsing a little (removed preg_quote and fixed regexp) 0.1.22 - now not only *array:foo* (and *array:^KEY*) is caught, but array:foo and array:^KEY as well Needed it because otherwise if clauses like {*array:a|array:b*} are not parsed. This required substitution of str_replace with with preg_replace which had no visible affect on perfomance. 0.1.21 - fixed some in var_value() (substr sometimes returns FALSE not empty string) everything is mb* now 0.1.20 - fixed trimming \r\n at the end of the template 0.1.19 - websun_parse_template_path() fixed to set current templates directory to the one of template specified 0.1.18 - KEY added 0.1.17 - fixed some in var_value() теперь по умолчанию корневой каталог шаблона - тот, в котором выполняется вызвавший функцию скрипт добавлены ^COUNT (0.1.13) добавлены if'ы: {*var_1|var_2|"строка"|1234(число)*} в if'ах добавлено сравнение с переменными: {?*a>b*}..{*a>b?*} {?*a>"строка"*}..{*a>"строка"*?} {?*a>3*}..{*a>3*?} */ class websun { public $vars; public $templates_root_dir; // templates_root_dir указывать без закрывающего слэша! public $templates_current_dir; function __construct($vars = array(), $templates_root = FALSE) { $this->vars = $vars; if ($templates_root) // корневой каталог шаблонов $this->templates_root_dir = $templates_root; else // если не указан, то принимается текущий каталог файла шаблонизатора // $this->templates_root_dir = dirname(__FILE__); // НЕТ! С 0.16 - текущий каталог рабочего скрипта $this->templates_root_dir = getcwd(); $this->templates_current_dir = $this->templates_root_dir . '/'; } function parse_template($template) { $template = preg_replace('/ \\/\* (.*?) \*\\/ /sx', '', $template); /**ПЕРЕПИСАТЬ ПО JEFFREY FRIEDL'У !!!**/ $template = str_replace('\\\\', "\x01", $template); // убираем двойные слэши $template = str_replace('\*', "\x02", $template); // и экранированные звездочки $template = preg_replace_callback( // дописывающие модули '/ {\* &(\w+) (?P\([^*]*\))? \*} /x', array($this, 'addvars'), $template ); $template = $this->find_and_parse_cycle($template); $template = $this->find_and_parse_if($template); $template = preg_replace_callback( // переменные, шаблоны и модули '/ {\* (.*?) \*} /x', array($this, 'parse_vars_templates_modules'), $template ); $template = str_replace("\x01", '\\\\', $template); // возвращаем двойные слэши обратно $template = str_replace("\x02", '*', $template); // а звездочки - уже без экранирования return $template; } // дописывание массива переменных из шаблона // (хак для Бурцева) function addvars($matches) { $module_name = 'module_'.$matches[1]; # ДОБАВИТЬ КЛАССЫ ПОТОМ $args = (isset($matches['args'])) ? explode(',', mb_substr($matches['args'], 1, -1) ) // убираем скобки : array(); $this->vars = array_merge( $this->vars, call_user_func_array($module_name, $args) ); // call_user_func_array быстрее, чем call_user_func return TRUE; } function var_value($string) { if ( mb_substr($string, 0, 1) == '"' AND mb_substr($string, -1) == '"' ) # скалярная величина return mb_substr($string, 1, -1); //if ($string !== '' AND !preg_match('/\D/', $string)) if (mb_strlen($string) AND !preg_match('/\D/', $string)) # опять же скалярная величина (число) // (0.1.17 - numeric() вместо этого нельзя, // т.к. тогда могу быть неполадки с разворотом циклов // - точки с числами могут встать хитрым образом // и перепутаться с этим), // а is_int() нельзя, т.к. возвращает // FALSE для строк типа '1' return $string; // можно делать if'ы: // {*var_1|var_2|"строка"|134*} // сработает первая часть, которая TRUE if (mb_strpos($string, '|') !== FALSE) { $f = __FUNCTION__; foreach (explode('|', $string) as $str) { // останавливаемся при первом же TRUE if ($val = $this->$f($str)) break; } return $val; } if (mb_substr($string, 0, 1) == '$') { // глобальная переменная $string = mb_substr($string, 1); $value = $GLOBALS; } else $value = $this->vars; // допустимы выражения типа {*var^COUNT*} // (вернет count($var)) ) if (mb_substr($string, -6) == '^COUNT') { $string = mb_substr($string, 0, -6); $return_mode = 'count'; } else $return_mode = FALSE; // default $rawkeys = explode('.', $string); $keys = array(); foreach ($rawkeys as $v) { if ($v !== '') $keys[] = $v; } // array_filter() использовать не получается, // т.к. числовой индекс 0 она тоже считает за FALSE и убирает // поэтому нужно сравнение с учетом типа // пустая строка указывает на корневой массив foreach ($keys as $k) { if (is_array($value) AND isset($value[$k]) ) $value = $value[$k]; else { $value = NULL; break; } } // в зависимости от $return_mode // действуем по-разному: return (!$return_mode) // возвращаем значение переменной // (обычный случай) ? $value // число элементов в массиве : ( is_array($value) ? count($value) : FALSE ) ; } function find_and_parse_cycle($template) { // пришлось делать специальную функцию, чтобы реализовать рекурсию return preg_replace_callback( '/ {%\* ([-\w$.]*) \*} (.*?) {\* \1 \*%} /sx', array($this, 'parse_cycle'), $template ); } function parse_cycle($matches) { $array_name = $matches[1]; $array = $this->var_value($array_name); if ( ! is_array($array) ) return FALSE; $parsed = ''; $dot = ( $array_name != '' AND $array_name != '$' ) ? '.' : ''; foreach ($array as $key => $value) { $parsed .= preg_replace( array(// массив поиска "/(?<=[*=<>|])$array_name\:\^KEY(?!\w)/", "/(?<=[*=<>|])$array_name\:/" ), array(// массив замены '"' . $key . '"', // preg_quote для ключей нельзя, $array_name . $dot . $key . '.' // т.к. в ключах бывает удобно ), // хранить некоторые данные, $matches[2] // а preg_quote слишком многое экранирует ); } $parsed = $this->find_and_parse_cycle($parsed); return $parsed; } function find_and_parse_if($template) { /* return preg_replace_callback( '/ { (\?!?) \* ( (?P [^<>=]* ) ( (?P[=<>])(?P[^*]*) )? ) \* } (?P .*? ) {\* \2 \* \1} /sx', array($this, 'parse_if'), $template ); */ return preg_replace_callback( '/ { (\?!?) \* ?# 1 ) ( ([^<>=]* ) # 3 - variable name ( ([=<>]) # 5 - sign ([^*]*) # 6 - value )? ) \* } (.*?) # 7 - body) ( {\* \2 \* \1} ) # закрывающее /sx', array($this, 'parse_if'), $template ); } function parse_if($matches) { // версия с именованными подшаблонами $variable_value = $this->var_value($matches[3]); // Замечание: variable_value устроена так, // что при пустой строке (вида {?*var=*}) // она возвращает корневой массив if (isset($matches[6])) { $compare_value = ($matches[6]) ? $this->var_value($matches[6]) : FALSE ; switch($matches[5]) { case '=': $check = ($variable_value == $compare_value); break; case '>': $check = ($variable_value > $compare_value); break; case '<': $check = ($variable_value < $compare_value); break; default: $check = ($variable_value == TRUE); } } else $check = ($variable_value == TRUE); $result = ($matches[1] == '?') ? $check : !$check ; $parsed_if = ($result) ? $this->find_and_parse_if($matches[7]) : '' ; return $parsed_if; } function parse_vars_templates_modules($matches) { // тут обрабатываем сразу всё - и переменные, и шаблоны, и модули $work = $matches[1]; $work = trim($work); // убираем пробелы по краям if (mb_substr($work, 0, 1) == '@') { // модуль {* @name(arg1,arg2) | template *} /* 0.1.24b - убираем named subpatterns $p = '/ ^ (?P[^(|]++) (?: \( (?P[^)]*+) \) \s* )? (?: \| \s* (?P.++) )? # это уже до конца $ /x'; if (preg_match( $p, mb_substr($work, 1), $m) ) { $tmp = $this->get_var_or_string($m['module']); $module_name = (mb_strpos($tmp, 'module_') === 0) ? $tmp : "module_$tmp" ; $args = ( isset($m['argstr']) ) ? array_map( array($this, 'get_var_or_string'), explode(',', $m['argstr']) ) : array(); $subvars = call_user_func_array($module_name, $args); if ( isset($m['tpl']) ) { $tplname = $this->get_var_or_string($m['tpl']); $html = $this->parse_child_template($tplname, $subvars); } else $html = $subvars; // шаблон не указан => модуль возвращает строку } else $html = ''; // вызов модуля сделан некорректно */ $p = '/ ^ ([^(|]++) # 1 - имя модуля (?: \( ([^)]*+) \) \s* )? # 2 - аргументы (?: \| \s* (.*+) )? # 3 - это уже до конца $ # (именно .*+, а не .++) /x'; if (preg_match( $p, mb_substr($work, 1), $m) ) { $tmp = $this->get_var_or_string($m[1]); $module_name = (mb_strpos($tmp, 'module_') === 0) ? $tmp : "module_$tmp" ; $args = ( isset($m[2]) ) ? array_map( array($this, 'get_var_or_string'), explode(',', $m[2]) ) : array(); $subvars = call_user_func_array($module_name, $args); if ( isset($m[3]) ) { $tplname = $this->get_var_or_string($m[3]); $html = $this->parse_child_template($tplname, $subvars); } else $html = $subvars; // шаблон не указан => модуль возвращает строку } else $html = ''; // вызов модуля сделан некорректно } elseif (mb_substr($work, 0, 1) == '+') { // шаблон - {* +*vars_var*|*tpl_var* *} $html = ''; $parts = explode( '|', mb_substr($work, 1) ); // плюс убрали $parts = array_map('trim', $parts); // убираем пробелы по краям if ( !isset($parts[1]) ) { // если нет разделителя (|) - значит, // передали только имя шаблона +template $tplname = $this->get_var_or_string($parts[0]); $html = $this->parse_child_template($tplname, $this->vars); // работаем с корневым массивом vars } else { $tplname = $this->get_var_or_string($parts[1]); $varname_string = mb_substr($parts[0], 1, -1); // убираем звездочки // {* +*vars* | шаблон *} - простая передача переменной шаблону // {* +*?vars* | шаблон *} - подключение шаблона только в случае, если vars == TRUE // {* +*%vars* | шаблон *} - подключение шаблона не для самого vars, а для каждого его дочернего элемента $indicator = mb_substr($varname_string, 0, 1); if ($indicator == '?') { if ( $subvars = $this->var_value( mb_substr($varname_string, 1) ) ) $html = $this->parse_child_template($tplname, $subvars); } elseif ($indicator == '%') { if ( $subvars = $this->var_value( mb_substr($varname_string, 1) ) ) { foreach ( $subvars as $row ) { $html .= $this->parse_child_template($tplname, $row); } } } else { $subvars = $this->var_value( $varname_string ); $html = $this->parse_child_template($tplname, $subvars); } } } elseif (mb_substr($work, 0, 1) == '=') { // константа $C = mb_substr($work, 1); $html = (defined($C)) ? constant($C) : ''; } else $html = $this->var_value($work); // переменная return $html; } function parse_child_template($subtplname, $subvars) { $c = __CLASS__; // нужен объект этого же класса - делаем $subobject = new $c($subvars, $this->templates_root_dir); $subobject->templates_current_dir = pathinfo( $this->template_real_path($subtplname), PATHINFO_DIRNAME ) . '/'; // важно получать subtemplate именно после установки templates_current_dir дочернего объекта $subtemplate = $this->get_template($subtplname); $html = $subobject->parse_template($subtemplate); return $html; } function get_var_or_string($str) { return ( mb_substr($str, 0, 1) == '*' AND mb_substr($str, -1) == '*') ? $this->var_value( mb_substr($str, 1, -1) ) : $str // если вокруг есть звездочки - значит, перменная ; // нет звездочек - значит, обычная строка-литерал // используется, в основном, // для получения имён шаблонов и модулей } function get_template($tpl) { if (!$tpl) return FALSE; $tpl_real_path = $this->template_real_path($tpl); // return rtrim(file_get_contents($tpl_real_path), "\r\n"); return preg_replace( '/\r\n$/', '', file_get_contents($tpl_real_path) ); // (убираем перенос строки, присутствующий в конце любого файла) } function template_real_path($tpl) { // функция определяет реальный путь к шаблону в файловой системе // первый символ пути к шаблону определяет тип пути // если в начале адреса есть / - интерпретируем как абсолютный путь ФС // если второй символ пути - двоеточие (путь вида C:/ - Windows) - // также интепретируем как абсолютный путь ФС // если есть ^ - отталкиваемся от $templates_root_dir // если $ - от $_SERVER[DOCUMENT_ROOT] // во всех остальных случаях отталкиваемся от каталога текущего шаблона - templates_current_dir $dir_indicator = mb_substr($tpl, 0, 1); $adjust_tpl_path = TRUE; if ($dir_indicator == '^') $dir = $this->templates_root_dir; elseif ($dir_indicator == '$') $dir = $_SERVER['DOCUMENT_ROOT']; elseif ($dir_indicator == '/') { $dir = ''; $adjust_tpl_path = FALSE; } // абсолютный путь для ФС else { if (mb_substr($tpl, 1, 1) == ':') // Windows - указан абсолютный путь - вида С:/... $dir = ''; else $dir = $this->templates_current_dir; $adjust_tpl_path = FALSE; // в обоих случаях строку к пути менять не надо } if ($adjust_tpl_path) $tpl = mb_substr($tpl, 1); $tpl_real_path = $dir . $tpl; return $tpl_real_path; } } function websun_parse_template_path($data, $template_path, $templates_root_dir = FALSE) { // функция-обёртка для быстрого вызова класса // принимает шаблон в виде пути к нему $W = new websun($data, $templates_root_dir); $tpl = $W->get_template($template_path); $W->templates_current_dir = pathinfo( $W->template_real_path($template_path), PATHINFO_DIRNAME ) . '/'; $string = $W->parse_template($tpl); return $string; } function websun_parse_template($data, $template_code, $templates_root_dir = FALSE) { // функция-обёртка для быстрого вызова класса // принимает шаблон непосредственно в виде кода $W = new websun($data, $templates_root_dir); $string = $W->parse_template($template_code); return $string; } ?>