Para facilitar a construção de templates (modelos) e a redação de conteúdo, propomos um sistema de marcação razoavelmente simples, porém, que se mostrará bastante poderoso.
Nossas marcações serão englobadas entre colchêtes (“[” E “]”). Por isto, a primeira tarefa será encontrá-los em meio ao conteúdo.
A função mais básica das marcações é inserir algum tipo de conteúdo em um local específico no documento. Exemplo:
<html lang="[$lang]">
Construir um documento a partir de um modelo exige mais que simplesmente inserir conteúdo. Em alguns casos precisaremos “saltar” um trecho. Poderemos fazer isto de forma semelhante às linguagens de programação:
[if:$title]<h1>[$title]</h1>[/if] [/if]
Imagine também uma situação onde uma lista com diversos itens precise ser percorrida. Para isto, podemos formar um laço “loop”, como no exemplo:
<ul> [loop] <li>[$name]</li> [/loop] </ul>
Neste exemplo, o salto deverá ser dado para trás, refazendo o mesmo trecho quantas vezes houverem itens na lista.
Vamos dividir a tarefa em duas etapas. Na primeira, recortaremos o modelo e as marcações e os colocaremos em um array. Em seguida, iremos criar o documento percorrendo o array. Apesar de ser um tanto complexo, isto facilitará o trabalho nos casos em que for necessário saltar para frente ou para trás, bastando pular para o item correspondente do array. Além disto, elimina o trabalho de reinterpretar várias vezes o mesmo trecho quando este tiver que ser percorrido em um loop.
As marcações regulares são compostas por uma palavra que identifica sua função. Opcionalmente, dois pontos (“:”) separam o nome da função de algum complemento. Veja os exemplos:
[loop] [var:$title] [key:my_keyword]
Para facilitar o trabalho, vamos criar marcações não-regulares – formas abreviadas de escrever algumas marcações muito comuns. A primeira delas – que já mostramos nos nossos exemplos – é a abreviação de [var:$variavel] para simplesmente [$variavel]. Outra que pode ser útil na redação de conteúdo é a abreviação de [key:palavra_chave] para [[palavra_chave]] – semelhante ao método utilizado na Wikipédia.
Adicionalmente, marcações que iniciarem com uma barra (“/”) terão seu nome automaticamente prefixado com “end”. Isto significa que [/if] será equivalente a [endif] assim como [/loop] será equivalente a [endloop].
O primeiro passo será recortar as marcações e colocá-las em um array. Para isto, iremos utilizar um índice ($index) que será acrescentado a cada nova marcação encontrada.
Durante a etapa de recortar as marcações, iremos distinguir aquelas que exigem um salto para frente, e aquelas que exigem um salto para trás. Nestes casos, iremos percorrer algumas instruções para anotar o índice do array para onde o salto deve ser dado.
Será possível aninhar marcações condicionais umas dentro das outras. O sistema não irá avisar caso houver falha nas marcações – o que poderá acarretar resultados indesejados. Porém, com a devida atenção, será possível criar modelos bastante flexíveis.
[if:$title]<div class="title"><h1>[text:$title]</h1> [if:$subtitle]<h2>[text:$subtitle]</h2> [/if] </div> [/if]
Quando decidi criar este método de marcação, pretendia fazer algo bastante simples – que fosse prático de utilizar em meio ao HTML e também em meio aos conteúdos. Decidi seguir um modelo semelhante ao utilizado na Wikipédia. Daí surgiu a utilização de colchetes – que até poderiam ser substituídos por chaves. Como os endereços web normalmente iniciam com http:, ftp: ou mailto: … utilizar os dois pontos “:” como separador seria prático. Mas acho que opiniões diferentes poderiam se manifestar! Inclusive sobre a utilização de parênteses… pois até agora não foi necessário, mas que, de repente, poderiam ser incluídos no projeto.
Apenas gostaria de lembrar que o importante é a simplicidade e que, quanto mais complicações inventarmos, correremos o sério risco de tornar o processamento muito lento. A idéia não é criar uma linguagem poderosa – para isto já temos o PHP. As aplicações e módulos devem preparar os conteúdos, restando aos modelos apenas indicar seus lugares.
No próximo capítulo, veremos como avaliar as expreções condicionais. Por enquanto, nossa função não poderá realizar nada além de inserir conteúdo. Veja um exemplo na referência desta função.
Na segunda etapa, ao percorrer o array resultante, chamaremos funções correspondentes a cada tipo de marcação encontrada para que realizem as respectivas tarefas. Também veremos estas funções mais adiante.
<?php $render = array ( 'buffer'=>'', // o documento resultante 'scope'=>array(), // os dados a serem inseridos no documento ); function render_tags ($string, &$render) { // render tags // Se $string não for uma string if (!is_string ($string) or !strlen ($string)) return ''; // O ponteiro que irá percorrer a string $pointer = 0; // O array a ser percorrido $array = array(); // O índice atual $index = 1; // onde anotaremos Os saltos $jump_if_index = 0; $jump_list_index = 0; $jump_loop_index = 0; $jump_if = array(); $jump_list = array(); $jump_loop = array(); // iremos trabalhar em um loop infinito while (true) { // loop recognize tags // procuremos por uma marcação $tag_open = strpos ($string, '[', $pointer); // se não encontrarmos uma marcação if ($tag_open === false) { // no more tags to render $array[$index]['buffer'] = substr ($string, $pointer); $array[$index]['type'] = ''; break; } // no more tags to render // Procuremos pelo final da marcação $tag_close = strpos ($string, ']', $tag_open); // se não encontrarmos o fim da marcação if ($tag_close === false) { // no closed tag $array[$index]['buffer'] = substr ($string, $pointer); $array[$index]['type'] = ''; break; } // no closed tag // O que vem antes da marcação será guardado no buffer $array[$index]['buffer'] = substr ($string, $pointer, $tag_open - $pointer); $array[$index]['type'] = ''; // vamos recortar o conteúdo da marcação $tag_open ++; $tag = substr ($string, $tag_open, $tag_close - $tag_open); // Vamos engolir uma quebra de linha após a marcação se houver $tag_close ++; if ($string[$tag_close] == "\r" and $string[$tag_close + 1] == "\n") $tag_close += 2; elseif ($string[$tag_close] == "\r" or $string[$tag_close] == "\n") $tag_close ++; // Vamos procurar por formas especiais da marcação // A marcação [] serve como escape de [ if (!strlen ($tag)) { // escape "[" $array[$index]['buffer'] .= '['; } // escape "[" // A marcação iniciada em [$...] equivale à [var:$...] elseif ($tag[0] == '$') { // found [$...] $array[$index]['type'] = 'var'; $array[$index]['arguments'] = $tag; } // found [$...] // As marcações regulares elseif (preg_match ('%^([/])?([a-z][a-z0-9_]*)([:](.*))?$%', $tag, $preg_match)) { // regular tag if ($preg_match[1] == '/') $array[$index]['type'] = 'end' . $preg_match[2]; else $array[$index]['type'] = $preg_match[2]; if (isset ($preg_match[4])) $array[$index]['arguments'] = $preg_match[4]; else $array[$index]['arguments'] = ''; // Vamos anotar os índices para as tags que saltam ou // caso for a tag [jump], vamos saltar até a marcação [/jump]$tag_type = $array[$index]['type']; switch ($tag_type) { // switch tag type case 'if': $jump_if_index ++; $jump_if[$jump_if_index] = $index; break; case 'elseif': case 'else': if (isset ($array[$jump_if[$jump_if_index]])) $array[$jump_if[$jump_if_index]]['jump'] = $index; $jump_if[$jump_if_index] = $index; break; case 'endif': $array[$index]['type'] = ''; if (isset ($array[$jump_if[$jump_if_index]])) $array[$jump_if[$jump_if_index]]['jump'] = $index; $jump_if_index --; break; case 'list': case 'mod': $jump_list_index ++; $jump_list[$jump_list_index] = $index; break; case 'endlist': case 'endmod': $array[$index]['type'] = ''; if (isset ($array[$jump_list[$jump_list_index]])) $array[$jump_list[$jump_list_index]]['jump'] = $index; $jump_list_index --; break; case 'loop': $array[$index]['type'] = ''; $jump_loop_index ++; $jump_loop[$jump_loop_index] = $index; break; case 'endloop': if (isset ($jump_loop[$jump_loop_index])) $array[$index]['jump'] = $jump_loop[$jump_loop_index]; $jump_loop_index --; break; case 'jump': $jump_close = strpos ($string, '[/jump]', $pointer); if ($jump_close === false) break; $array[$index]['buffer'] .= substr ($string, $tag_close, $jump_close - $tag_close); $tag_close = $jump_close + 7; // Vamos engolir uma quebra de linha após a marcação se houver if ($string[$tag_close] == "\r" and $string[$tag_close + 1] == "\n") $tag_close += 2; elseif ($string[$tag_close] == "\r" or $string[$tag_close] == "\n") $tag_close ++; } // switch tag type } // regular tag // outras formas de tags irregulares // A marcação [[...]] equivale à [key:...] elseif ($tag[0] == '[') { // found '[[...]]' if ($string[$tag_close+1] != ']') { // wrong tag $array[$index]['buffer'] .= substr ($string, $pointer, $tag_close - $pointer) . ']'; } // wrong tag else { // key tag $array[$index]['type'] = 'key'; $array[$index]['arguments'] = $tag; $tag_close ++; } // key tag } // found '[[...]]' else { // irregular tag } // irregular tag $pointer = $tag_close; $index ++; } // loop recognize tags // Agora, vamos percorrer o array $index = 1; while (isset ($array[$index])) { // loop array $render['buffer'] .= $array[$index]['buffer']; switch ($array[$index]['type']) { // switch tag type // Se a marcação for do tipo var, vamos colocar o seu conteúdo no documento resultante case 'var': $arguments = $array[$index]['arguments']; if ($arguments[0] == '$') $arguments = substr ($arguments, 1); if (isset ($render['scope'][$arguments])) $render['buffer'] .= $render['scope'][$arguments]; break; case 'if': while (!condition ($array[$index]['arguments'], $render['scope']) and isset ($array[$index]['jump'])) { // jump $index = $array[$index]['jump']; } // jump break; case 'elseif': case 'else': while (isset ($array[$index]['jump'])) { // jump to endif $index = $array[$index]['jump']; } // jump to endif break; case '': break; default: $function = 'tag_' . $array[$index]['type']; if (function_exists ($function)) $function ($render, $array, $index); } // switch tag type $index ++; } // loop array } // render tags ?>