<?php
/*!
\section intro_sec Классы для работы с шаблонами
Эта группа содержит классы, которые обеспечивает работу с системой шаблонов
*/
/*!
\defgroup template Классы, которые обеспечивает работу с системой шаблонов
С помощью классов этой группы происходит работа с шаблонами
@todo
- Пересмотреть работу с кэшем. Что то мне не нравится она.
- Работа с XSLT? Или работать с ним через стандартные функции?
*/
//! Класс, который определяет необходимые методы для работы с информационными блоками в рамках точки входа html шаблона.
/*!
@ingroup template
@see template_class
*/
class outlet{
private $outlet;
private $start;
private $end;
private $render = null;
private $plug_list = array();
private $plug_order = array();
//! Конструктор
/*!
@param outlet строка, содержащая тело точки входа
@param start строка, содержащая открывающий блок
@param end строка, содержащая закрывающий блок
*/
public function __construct($outlet=null,$start='',$end=''){
if($outlet===null){
throw new Exception("Точка ввода не задана.",E_ERROR);
}
$this->outlet = (string)$outlet;
$this->start = (string)$start;
$this->end = (string)$end;
}
//! Возвращает тело точки входа
/*!
@return тело точки входа
*/
public function get_outlet(){
return $this->outlet;
}
//! Выполняет сборку всех блоков информационного наполнения, присвоенных данной точке входа
/*!
@return собранную точку входа
*/
public function render(){
$render=$this->start;
for($i=0;$i<count($this->plug_order);$i++){
$render.=$this->plug_list[$this->plug_order[$i]];
}
$render.=$this->end;
return $render;
}
//! Присваивает именованный информационный блок точке входа.
/*!
@param name имя инфомационного блока.
@param content строка, содержащая информационное наполнение блока.
@param order порядок блока в стеке сборки. Нумерация начинается с 0.
*/
public function plug($name,$content,$order=null){
if(!is_string($name)){
$name = (string)$name;
}
if(!is_string($content)){
$content = (string)$content;
}
$this->plug_list[$name]=$content;
$this->set_order($name,$order);
}
//! Присваивает именованный информационный блок точке входа.
/*!
@param name имя инфомационного блока.
@param content строка, содержащая информационное наполнение блока.
@param order порядок блока в стеке сборки. Нумерация начинается с 0.
*/
public function unplug($name){
if(!is_string($name)){
$name = (string)$name;
}
if(!array_key_exists($name,$this->plug_list)){
throw new Exception("Блок ".$name." не найден.",E_ERROR);
}
unset($this->plug_list[$name]);
if(($position = array_search($name,$this->plug_order))===FALSE){
throw new Exception("Порядок вывода блока ".$name." не определен.",E_ERROR);
}
array_splice($this->plug_order,$position,1);
}
//! Определяет порядок информационного блока в стеке сборки.
/*!
@param name имя инфомационного блока.
@param order порядок блока в стеке сборки. Нумерация начинается с 0. Если номер блока в сборке больше чем текущее количество элементов, то присваивается номер следующий за максимальным.
*/
public function set_order($name,$order=null){
if(!is_string($name)){
$name = (string)$name;
}
if(!is_int($order)&&(!is_null($order))){
$order = (int)$order;
}
if(($position = array_search($name,$this->plug_order))!==FALSE){
array_splice($this->plug_order,$position,1);
}
if($order===null){
$this->plug_order[] = $name;
}else{
if($order>=count($this->plug_order)){
$this->plug_order[] = $name;
}else{
$part = array_splice($this->plug_order,$order);
$this->plug_order[] = $name;
$this->plug_order = array_merge($this->plug_order,$part);
}
}
}
}
//! Класс, который определяет необходимые методы для работы с html шаблонами.
/*!
@ingroup template
@see template_class
*/
class template_class{
private $rule = array();
public $rule_list = array();
public $rule_order = array();
public $template=false;
private $name;
private $render=null;
//! Конструктор
/*!
@param name имя шаблона
@param data строка содержащая шаблон или путь к файлу, содержащему шаблон, если is_file true
@param is_file если равен true, то data считается путем к файлу шаблона
@param cash будет ли использовано кеширование. Для его использования необходим подключенный модуль кеширования.
*/
public function __construct($name=null,$data=null,$is_file=true,$cash=false){
if($name===null){
throw new Exception("Нет задано имя шаблона.",E_ERROR);
}
if($data===null){
throw new Exception("Нет тела шаблона.",E_ERROR);
}
$this->name=(string)$name;
if($is_file){
if(($this->template = file_get_contents($data))===false){
throw new Exception("Не удалось прочитать файл шаблона.",E_ERROR);
}
}else{
$this->template = $data;
}
if(in_array("cache",classget_declared_classes())){
}
}
//! Определяет порядок обработки правил
/*!
@param order номер в стеке обработки правил. Нумерация начинается с 0.
@param name имя правила
*/
private function set_order($order,$name){
if($order===null){
$this->rule_order[] = $name;
}else{
if($order>=count($this->rule_order)){
$this->rule_order[] = $name;
}else{
$part = array_splice($this->rule_order,$order);
$this->rule_order[] = $name;
$this->rule_order = array_merge($this->rule_order,$part);
}
}
}
//! Создает правило содержащее точки входа формата <!--[CMS][P]Имя точки-->
/*!
@param name имя правила
@param order номер в стеке обработки правил. Нумерация начинается с 0. Если null или не задана, то присваивается следующий за максимальным порядковый номер
*/
public function point($name,$order=null){
$pattern="|<!--\[CMS\]\[P\][\s]*([^\s]+.*)[\s]*-->|iUus";
if(array_key_exists($pattern,$this->rule)){
throw new Exception("Правило уже применено к шаблону. Имя существующего правила: ".$this->rule[$pattern],E_ERROR);
}
$this->rule[$pattern] = $name;
$result=array();
if(($count = preg_match_all($pattern,$this->template,$result,PREG_PATTERN_ORDER))===false){
throw new Exception("Ошибка при выборке по регулярному выражению.",E_ERROR);
}
if($count==0){
throw new Exception("Не найдено точек ввода, соответствующих регулярному выражению.",E_ERROR);
}
foreach($result[1] as $key=>$value){
$this->rule_list[$name][$result[1][$key]] = new outlet($result[0][$key]);
}
$this->set_order($order,$name);
}
//! Создает правило содержащее точки входа формата <!--[CMS][B]Имя блока-->Содержимое блока<!--[CMS][B]/Имя блока--> При этом содержимое блока будет помещено в точку входа с именем "content"
/*!
@param name имя точки входа
@param order номер в стеке обработки правил. Нумерация начинается с 0. Если null или не задана, то присваивается следующий за максимальным порядковый номер
*/
public function block($name,$order=null){
$pattern = "
@
(<!--\[CMS\]\[B\](?!/)[\s]*([^\s]+.*)[\s]*-->)
(.*)
(<!--\[CMS\]\[B\]/[\s]*\\2[\s]*-->)
@iUuxs";
if(array_key_exists($pattern,$this->rule)){
throw new Exception("Правило уже применено к шаблону. Имя существующего правила: ".$this->rule[$pattern],E_ERROR);
}
$this->rule[$pattern] = $name;
$result=array();
if(($count = preg_match_all($pattern,$this->template,$result,PREG_PATTERN_ORDER))===false){
throw new Exception("Ошибка при выборке по регулярному выражению.",E_ERROR);
}
if($count==0){
throw new Exception("Не найдено точек ввода, соответствующих регулярному выражению.",E_ERROR);
}
foreach($result[1] as $key=>$value){
if(strpos($result[2][$key],'<!--[CMS]')!==false){
continue;
}
$content = substr($result[0][$key],strlen($result[1][$key]),strlen($result[0][$key])-strlen($result[4][$key])-strlen($result[1][$key]));
$this->rule_list[$name][$result[2][$key]] = new outlet($result[0][$key]);
if(!empty($content)){
$this->plug($name,$result[2][$key],'content',$content);
}
}
$this->set_order($order,$name);
}
//! Создает правило, содержащее точки входа, выбранные по тегу.
/*!
@param name имя стека точек входа. В рамках стека идет числовая нумерация.
@param tag имя тега
@param order номер в стеке обработки правил. Нумерация начинается с 0. Если null или не задана, то присваивается следующий за максимальным порядковый номер
*/
public function tag($name,$tag,$order=null){
$pattern="
@
<(".$tag.")(?:>|\s[^>/]*>)
@iUuxs";
$this->html_tag_block($name,$pattern,$order);
}
//! Создает правило, содержащее точки входа, выбранные по атрибуту тега.
/*!
@param name имя стека точек входа. В рамках стека идет числовая нумерация.
@param attr имя атрибута.
@param order номер в стеке обработки правил. Нумерация начинается с 0. Если null или не задана, то присваивается следующий за максимальным порядковый номер.
*/
public function attr($name,$attr,$order=null){
$pattern="
@
<([^\s/!>]+?)[^>]*[\s]".$attr."=(\'|\")([^>\"\']*)\\2[^>/]*>
@iUuxs";
$this->html_tag_block($name,$pattern,$order);
}
//! Создает правило, содержащее точки входа, выбранные по значению атрибута тега.
/*!
@param name имя стека точек входа. В рамках стека идет числовая нумерация.
@param attr имя атрибута тега.
@param value значения атрибута тега.
@param order номер в стеке обработки правил. Нумерация начинается с 0. Если null или не задана, то присваивается следующий за максимальным порядковый номер.
*/
public function attr_value($name,$attr,$value,$order=null){
$pattern="
@
<([^\s/!>]+?)[^>]*[\s]".$attr."=(\'|\")".$value."\\2[^>/]*>
@iUuxs";
$this->html_tag_block($name,$pattern,$order);
}
//! Создает правило, содержащее точки входа, выбранные по одному из значений атрибута тега, перечисленных через пробел.
/*!
@param name имя стека точек входа. В рамках стека идет числовая нумерация.
@param attr имя атрибута тега.
@param value значения атрибута тега.
@param order номер в стеке обработки правил. Нумерация начинается с 0. Если null или не задана, то присваивается следующий за максимальным порядковый номер.
*/
public function attr_approximate_value($name,$attr,$value,$order=null){
$pattern="
@
<([^\s/!>]+?)[^>]*[\s]".$attr."=(\'|\")(?:[^\"\']+[\s]".$value."|".$value.")(?:\\2|[\s][^\"\']+\\2)[^>/]*>
@iUuxs";
$this->html_tag_block($name,$pattern,$order);
}
//! Метод создает правило, по заданному регулярному выражению. Используется для создания правил с неуникальными точками входа. Каждая из точек снабжается уникальным маркером, что делает невозможным промежуточное кеширование результатов работы функции.
private function html_tag_block($name,$pattern,$order=null){
if(array_key_exists($pattern,$this->rule)){
throw new Exception("Правило уже применено к шаблону. Имя существующего правила: ".$this->rule[$pattern],E_ERROR);
}
$this->rule[$pattern] = $name;
$result=array();
if(($count = preg_match_all($pattern,$this->template,$result,PREG_OFFSET_CAPTURE))===false){
throw new Exception("Ошибка при выборке по регулярному выражению.",E_ERROR);
}
if($count==0){
throw new Exception("Не найдено точек ввода, соответствующих регулярному выражению.",E_ERROR);
}
$i=0;
$additional_offset = 0;
foreach($result[1] as $key=>$value){
$pointer = "<!--[RULE]".$name."[OUTLET]".$i."-->";
$pointer_length = strlen($pointer);
$this->template = substr_replace($this->template,$pointer,$result[0][$key][1]+$additional_offset,0);
$start_position = $result[0][$key][1]+$additional_offset;
$open_tag = $result[0][$key][0];
$open_tag_length = strlen($open_tag);
$open_tag_common = "<".$result[1][$key][0];
$close_tag = "</".$result[1][$key][0].">";
$close_tag_length = strlen($close_tag);
$end_position = stripos($this->template,$close_tag,$start_position+$open_tag_length);
$content= substr($this->template,$start_position,$end_position+$close_tag_length-$start_position);
while(substr_count($content,$close_tag)!=substr_count($content,$open_tag_common)){
$start_position=$end_position+$close_tag_length;
if(($end_position = stripos($this->template,$close_tag,$start_position))===false){
break;
}
$content.= substr($this->template,$start_position,$end_position+$close_tag_length-$start_position);
}
$this->rule_list[$name][$i] = new outlet($content,$open_tag,$close_tag);
$content=substr($content,$open_tag_length+$pointer_length,strlen($content)-$close_tag_length-$open_tag_length-$pointer_length);
if(!empty($content)){
$this->plug($name,$i,'content',$content,$open_tag,$close_tag);
}
$i++;
$additional_offset+=$pointer_length;
}
$this->set_order($order,$name);
}
//! Удаляет правило
/*!
@param name имя правила, которое будет удалено
*/
public function remove_rule($name){
if(!array_key_exists($name,$this->rule_list)){
throw new Exception("Правило '".$name."' не найдено.",E_ERROR);
}
unset($this->rule_list[$name]);
$pattern="
@
<!--\[RULE\]".$name."\[OUTLET\].*(?=-->)-->
@iUuxs";
$this->template=preg_replace($pattern,"",$this->template);
array_splice($this->rule_order,array_search($name,$this->rule_order),1);
}
//! Добавляет блок с информационным наполнением в точку входа правила
/*!
@param rule имя правила
@param outlet имя точки входа
@param name имя блока
@param data строка, содержащая информационное наполнение блока.
@param order номер в стеке обработки точек входа. Нумерация начинается с 0.
*/
public function plug($rule=null,$outlet=null,$name=null,$data=null,$order=null){
if(
is_null($rule)||
is_null($outlet)||
is_null($name)||
is_null($data)
){
throw new Exception("Не все аргументы заданы.",E_ERROR);
}
if(!array_key_exists($rule,$this->rule_list)){
throw new Exception("Правило '".$rule."' не найдено.",E_ERROR);
}
if(!array_key_exists($outlet,$this->rule_list[$rule])){
throw new Exception("Точка подключения '".$outlet."' не найдена.",E_ERROR);
}
$this->rule_list[$rule][$outlet]->plug($name,$data,$order);
}
//! Производит сборку шаблона
/*!
@return собранное тело шаблона. Так же оно сохраняется в свойстве класса render
*/
public function render(){
$render = $this->template;
for($i=0;$i<count($this->rule_order);$i++){
foreach($this->rule_list[$this->rule_order[$i]] as $key=>$value){
$render=str_ireplace($value->get_outlet(),$value->render(),$render);
}
}
return $this->render = $render;
}
//! Возвращает текущую сборку шаблона или null в случае ее отсутствия
public function get_render(){
return $this->render;
}
}
?>