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(// массив поиска "/(?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['varname']); // применять variable_value // для получения значения $compare_value // нельзя при пустой строке, // т.к. variable_value устроена так, // что при пустой строке // она возвращает корневой массив $compare_value = ($matches['value']) ? $this->var_value($matches['value']) : FALSE ; if (isset($matches['sign']) AND $matches['sign']) { switch($matches['sign']) { case '=': $check = ($variable_value == $compare_value); break; case '>': $check = ($variable_value > $compare_value); break; case '<': $check = ($variable_value < $compare_value); break; } } else $check = ($variable_value == TRUE); $result = ($matches[1] == '?') ? $check : !$check ; $parsed_if = ($result) ? $this->find_and_parse_if($matches['body']) : '' ; return $parsed_if; } */ function parse_if($matches) { // версия с именованными подшаблонами $variable_value = $this->var_value($matches[3]); // применять variable_value // для получения значения $compare_value // нельзя при пустой строке, // т.к. variable_value устроена так, // что при пустой строке // она возвращает корневой массив if (isset($matches[6]) AND $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; } } 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; } ?>