Formatação de texto para html

Uma tarefa importante e frequente na publicação na Internet é a conversão de textos para HTML.

Não esperamos que o usuário tenha algum conhecimento do HTML. Além disto, é um trabalho muito massante escrever os textos em meio à uma profusão de marcações – que atrapalham inclusive alguma alteração posterior.

Ao bom estilo “wiki”, vamos criar uma função que reconhece os parágrafos, inserindo automaticamente as marcações para os títulos, listas e alguma coisa mais.

Para “potencializar” ainda mais o nosso formatador, vamos usar a função para filtrar o HTML que criamos anteriormente – filter_html().

Pode acontecer que, ao submeter um texto em um formulário, as linhas sejam quebradas em lugares indesejados. Para evitar confusão, vamos considerar como separação de parágrafo, uma linha em branco.

Nossa função irá pegar o primeiro caractére do parágrafo e fazer uma análize. Porém, algumas marcações poderão abranger múltiplos parágrafos, por isto, nosso método de percorrer os parágrafos não é o mais simples: ao invés de usarmos um simples explode(), que poderia nos entregar os parágrafos em um array, utilizaremos um ponteiro para procurar os parágrafos e os recortaremos um a um.

Teremos um conjunto de marcações padrão para serem utilizadas durante a formatação, mas aceitaremos como parâmetro marcações alternativas que, se forem informadas, serão utilizadas no lugar das marcações padrão.

Vamos à função :

<?php

    function text2html ($string, $filter_html_mode=1, $alternative_marks=false)
        { // text2html

            // As marcações utilizadas por padrão
            static $default_marks = array (
            'p_open'=>'<p>',
            'p_close'=>'</p>',
            'h1_open'=>'<h1>',
            'h1_close'=>'</h1>',
            'h2_open'=>'<h2>',
            'h2_close'=>'</h2>',
            'h3_open'=>'<h3>',
            'h3_close'=>'</h3>',
            'h4_open'=>'<h4>',
            'h4_close'=>'</h4>',
            'h5_open'=>'<h5>',
            'h5_close'=>'</h5>',
            'h6_open'=>'<h6>',
            'h6_close'=>'</h6>',
            'ul_open'=>'<ul>',
            'ul_close'=>'</ul>',
            'ol_open'=>'<ol>',
            'ol_close'=>'</ol>',
            'al_open'=>'<ol style="list-style-type:lower-latin">',
            'al_close'=>'</ol>',
            'li_open'=>'<li>',
            'li_close'=>'</li>',
            'pre_open'=>'<pre>',
            'pre_close'=>'</pre>',
            'code_open'=>'<code><pre>',
            'code_close'=>'</pre></code>',
            'style_open'=>'<style type="text/css">',
            'style_close'=>'</style>',
            'script_open'=>'<script type="text/javascript">',
            'script_close'=>'</script>',
            'hr'=>'<hr>'
        );

    // Permite marcações alternativas
    is_array ($alternative_marks)? $marks = array_merge ($default_marks, $alternative_marks) :    $marks = $default_marks;

    // Converte quebras de linhas Windows para Linux
    $string = str_replace ("\r\n", "\n", $string);
    // o cumprimento total da string
    $strlen = strlen ($string);
    // Os ponteiros indicando o início e o fim de um parágrafo
    $paragraph_start = 0;
    $paragraph_end = 0;
    // O resultado final
    $result = '';

    // enquanto o ponteiro não alcançar o final da string
    while ($paragraph_start < $strlen)
        { // move pointer

            // um parágrafo é delimitado por duas quebras de linha consecutivas:
            $paragraph_end = strpos ($string, "\n\n", $paragraph_start);
            if ($paragraph_end === false)
               $paragraph_end = $strlen;
            else
            $paragraph_end += 2;

            // pega o primeiro caractére do parágrafo
            $char = $string[$paragraph_start];
            switch ($char)
                { // switch char

                    // Ignora os caractéres
                    case "\r":
                    case "\n":
                    case "\t":
                    $paragraph_end = $paragraph_start + 1;
                    break;

                    // Listas
                    case '*':
                    case '#':
                    case '@':
                    $paragraph = substr ($string, $paragraph_start, $paragraph_end - $paragraph_start);
                   // Separa as linhas do parágrafo
                   $lines = explode ("\n", $paragraph . "\n");
                   $last = '';
                   $last_level = 0;
                   // Percorre as linhas deste parágrafo
                   foreach ($lines as $line)
                       { // each line
                            // Quantos símbolos existem no início desta linha
                            $current_level = strspn ($line, '*#@');
                            // Recorta os símbolos encontrados para análize posterior
                            $current = substr ($line, 0, $current_level);
                            // Retira os símbolos do início da linha
                            $line = substr ($line, $current_level);
                            $line = trim ($line);
                            $line = filter_html ($line, $filter_html_mode);
                            // Se preferir: $line = filter_html (trim (substr ($line, $current_level)), $filter_html_mode);

                            // Se não houverem símbolos no início desta linha, trata-se de uma 
                            // quebra acidental, o que nos leva a emendar esta linha com a linha anterior
                            if (!$current_level and strlen ($line))
                                $result .= ' ' . $line;
                            else
                                { // valid line

                                    // Vamos comparar os símbolos da linha atual com a linha anterior
                                    // para determinar até que ponto são iguais
                                    for ($equal = 0; $equal < $last_level and $equal < $current_level and $current[$equal] == $last[$equal]; $equal ++)
                                        { } // compare levels

                                     // Se o resultado indica que precisamos descer algum nível da lista
                                     for ($level = $last_level; $level > $equal; $level --)
                                         { // loop level down
                                             $char = $last[$level - 1];
                                             if ($char == '*')
                                                 $result .= $marks['li_close'] . "\n" . $marks['ul_close'] . "\n";
                                             elseif ($char == '#')
                                                 $result .= $marks['li_close'] . "\n" . $marks['ol_close'] . "\n";
                                             else
                                                 $result .= $marks['li_close'] . "\n" . $marks['al_close'] . "\n";
                                          } // loop level down

                                      // Se fechamos uma lista, a linha atual deverá ser criada como um novo item
                                      if ($last_level > $equal and $equal)
                                          { // new item after close a list
                                               $result .= $marks['li_close'] . "\n" . $marks['li_open'];
                                          } // new item after close a list

                                      // Se o prefixo da lista anterior for idêntico ao desta, 
                                      // simplesmente criamos um novo item
                                      if ($last_level == $equal and $current_level == $equal and $equal)
                                          { // item in same level
                                              $result .= $marks['li_close'] . "\n" . $marks['li_open'];
                                          } // item in same level

                                      // Se for preciso criar sublistas
                                      for ($level = $equal; $level < $current_level; $level ++)
                                          { // level up
                                              if ($level)
                                                  $result .= "\n";
                                                  $char = $current[$level];
                                              if ($char == '*')
                                                  $result .= $marks['ul_open'] . "\n" .                   $marks['li_open'];
                                              elseif ($char == '#')
                                                  $result .= $marks['ol_open'] . "\n" . $marks['li_open'];
                                              else
                                                  $result .= $marks['al_open'] . "\n" . $marks['li_open'];
                                           } // level up

                                       // Finalmente, colocamos o conteúdo desta linha
                                       $result .= $line;
                                       // Preparamos a próxima iteração anotando os itens desta linha como
                                       // sendo os símbolos da linha anterior
                                       $last = $current;
                                       $last_level = $current_level;
                                   } // valid line
                               } // each line
                           break;

                           // Cabeçálhos - ou Títulos
                           case '=':
                           $paragraph = substr ($string, $paragraph_start, $paragraph_end - $paragraph_start);
                           // Vamos ver quantos símbolos = tem no início do parágrafo
                           $level = strspn ($paragraph, '=');
                           // Se forem de 1 a 6
                           if ($level <= 6)
                               { // valid header level
                                    // Vamos filtrar o parágrafo, removendo os sinais de = no início e no final
                                    $paragraph = trim ($paragraph, "= \r\n\t");
                                    $paragraph = filter_html ($paragraph, $filter_html_mode);

                                    $result .= $marks['h' . $level . '_open'] . $paragraph .                                                         $marks['h' . $level . '_close'] . "\n";
                                } // valid header level

                            // Se tiverem mais de 6 sinais de =, será tratado como um parágrafo normal
                            else
                                $result .= $marks['p_open'] . filter_html (trim ($paragraph),  $filter_html_mode) . $marks['p_close'] . "\n";
                                break;

                                // Um separador horizontal
                                case '-':
                                $paragraph = substr ($string, $paragraph_start, $paragraph_end - $paragraph_start);
                                // Quantos hifens ("-") tem no parágrafo
                                $level = strspn ($paragraph, '-');
                                    // Vamos limpar o parágrafo
                                    $clean = trim ($paragraph, "- \r\n\t");
                                    // Somente se houverem mais de 4 hifens e se não houver restado nada após a limpeza
                                if ($level > 4 and !strlen ($clean))
                                    $result .= $marks['hr'] . "\n";
                                    // Caso contrário, este será mais um parágrafo comum
                                else
                                    $result .= $marks['p_open'] . filter_html (trim ($paragraph),                        $filter_html_mode) . $marks['p_close'] . "\n";
                                    break;

                                    // Linhas pré-formatadas terão o HTML escapado
                                    case ' ':
                                    $paragraph = substr ($string, $paragraph_start,                                                                               $paragraph_end - $paragraph_start);
                                    $result .= $marks['pre_open'] . "\n" . str_replace ('<',    '&lt;', trim ($paragraph, "\r\n")) . "\n" . $marks['pre_close'] . "\n";
                                    break;

                                    // procura por marcações que alteram o método de formatação
                                        case '[':
                                            $tag_start = $paragraph_start + 1;
                                        // Procura por ]
                                    $tag_end = strpos ($string, ']', $tag_start);
                                    // Se for encontrado
                                if (is_int ($tag_end))
                                    { // end of tag found
                                        // Recortamos a tag
                                        $tag = substr ($string, $tag_start, $tag_end - $tag_start);
                                        list ($tag_type) = explode (':', $tag);
                                        $tag_original = $tag_type;
                                        // Vamos analizar seu conteúdo
                                        switch ($tag_type)
                                            { // switch tag
                                                case 'style':
                                                case 'script':
                                                // Se não for permitido script ou style, as marcas serão convertidas
                                                if ($filter_html_mode != 2)
                                                    $tag_type = 'pre';
                                                    case 'code':
                                                    case 'pre':

                                                    // Vamos procurar pela marca de fechamento
                                                    $close_tag = '[/' . $tag_original . ']';
                                                    $close_tag_length = strlen ($close_tag);
                                                    $close_tag_pos = strpos ($string, $close_tag, $tag_end);
                                                    // Se a encontrarmos
                                                if ($close_tag_pos)
                                                    { // close tag found
                                                        $paragraph_end = $close_tag_pos +   $close_tag_length;
                                                        $tag_end ++;
                                                        $paragraph = substr ($string, $tag_end,    $close_tag_pos - $tag_end);

                                                        // Se o conteudo for do tipo code ou pre, escapamos o HTML
                                                    if ($tag_type == 'code' or $tag_type == 'pre')
                                                        $paragraph = str_replace ('<', '&lt;', $paragraph);

                                                        $result .= $marks[$tag_type . '_open'] .   $paragraph . $marks[$tag_type . '_close'] . "\n";
                                                        break 2;
                                                    } // close tag found

                                                // outras marcações serão consideradas como um parágrafo normal
                                                // simplesmente deixando o fluxo do programa prosseguir
                                            } // switch tag
                                        } // end of tag found

                                    // Um parágrafo normal
                                    default:
                                    $result .= $marks['p_open'] . filter_html (trim (substr ($string, $paragraph_start, $paragraph_end - $paragraph_start)), $filter_html_mode) .                  $marks['p_close'] . "\n";
                                } // switch char

                            // Movemos o ponteiro adiante
                            $paragraph_start = $paragraph_end;
                        } // move pointer

                    // converte quebras de linhas Linux para Windows
                    $result = str_replace ("\n", "\r\n", $result);
                    return $result;
                } // text2html

?>

Sobre o Autor

Redes Sociais:

Deixe seu comentário

X