Programação Por Equipe DevBras

Capa do artigo: Quebra de linha que some no HTML: \n, nl2br, pre-line e o escape na ordem certa

Quebra de linha que some no HTML: \n, nl2br, pre-line e o escape na ordem certa

Entenda por que o \n do banco vira uma linha só na página, mostre parágrafos com nl2br ou white-space: pre-line, escape antes de converter e saiba quando o texto deve ser texto puro.

O cliente escreveu o pedido de orçamento em três parágrafos: o que precisa, o prazo e o endereço da obra. No banco, o texto está lá com as quebras. No painel administrativo, aparece num bloco único, com o endereço grudado no prazo. Você tenta echo $texto . '\n' e nada muda. A resposta curta: o HTML trata quebra de linha como um espaço qualquer. Para a quebra aparecer, você converte o texto em tags (<br> ou <p>) depois de escapar o conteúdo, ou usa o CSS white-space: pre-line no elemento que mostra o texto. Em PHP, a forma curta é nl2br(htmlspecialchars($texto, ENT_QUOTES, 'UTF-8')), nessa ordem.

A ordem não é detalhe. Converter primeiro e escapar depois mostra <br> escrito na tela. Não escapar abre a porta para o usuário injetar HTML e script no seu painel.

Por que o navegador ignora o \n

No HTML, espaços, tabulações e quebras de linha seguidas são colapsados num único espaço. Isso é de propósito: permite indentar o código-fonte da página sem afetar o que aparece. O efeito colateral é que o texto vindo do banco, com seus caracteres de quebra, é desenhado como uma linha corrida. A quebra existe no conteúdo; o navegador só não a desenha.

Em outros destinos, a mesma quebra funciona. Um arquivo .txt, um CSV, o terminal e um e-mail em texto puro respeitam o caractere de quebra. É por isso que o mesmo texto parece certo no e-mail de confirmação e errado no painel.

Há ainda a questão de qual caractere é a quebra. Linux usa \n, o Windows tradicionalmente usa \r\n, e o navegador, ao enviar um textarea, normaliza as quebras para \r\n. Código que procura só \n ou só \r\n acaba tratando metade dos casos.

A pegadinha das aspas no PHP

Em PHP, '\n' entre aspas simples é literalmente uma barra e a letra n. Só "\n" entre aspas duplas vira o caractere de quebra. Muita gente "testa a quebra" com aspas simples, vê \n escrito na tela e conclui que o problema é outro.

echo 'Linha 1\nLinha 2';  // imprime: Linha 1\nLinha 2
echo "Linha 1\nLinha 2";  // imprime duas linhas no terminal,
                          // mas uma linha só no navegador

Como mostrar as quebras do jeito certo

  1. Defina o que o campo guarda: texto puro, como o orçamento, ou HTML, como o corpo de um artigo vindo de editor rico.
  2. Guarde o texto puro como o usuário digitou, sem converter nada na entrada.
  3. Na saída para HTML, escape primeiro com htmlspecialchars.
  4. Depois converta as quebras: nl2br para quebras simples, ou divida em parágrafos.
  5. Para e-mail em texto puro, CSV ou arquivo, use o texto original, sem tags.

Opção 1: nl2br, a mais simples

<?php
$texto = $pedido['descricao'];
?>
<div class="descricao">
  <?= nl2br(htmlspecialchars($texto, ENT_QUOTES, 'UTF-8'), false) ?>
</div>

O nl2br insere um <br> antes de cada quebra e reconhece as variações \r\n, \n e \r. O segundo argumento false gera <br> em vez de <br />, que é a forma comum em HTML5; as duas funcionam. Se o cliente digitar <script> na descrição, o htmlspecialchars já transformou os sinais em entidades antes, e o texto aparece como texto.

Opção 2: parágrafos de verdade

Quando o texto tem parágrafos separados por linha em branco, transformar cada bloco em <p> dá espaçamento melhor do que uma pilha de <br>.

<?php
function textoParaHtml(string $texto): string
{
    $blocos = preg_split('/\R\s*\R/', trim($texto));
    $html = '';
    foreach ($blocos as $bloco) {
        $seguro = htmlspecialchars($bloco, ENT_QUOTES, 'UTF-8');
        $html .= '<p>' . nl2br($seguro, false) . "</p>\n";
    }
    return $html;
}

Na expressão regular, \R casa qualquer sequência de quebra, então a função funciona com texto vindo do Windows ou do Linux. Duas quebras com espaços no meio separam parágrafos; uma quebra simples dentro do bloco vira <br>. O escape continua acontecendo antes de qualquer tag ser adicionada.

Opção 3: deixar o CSS desenhar as quebras

.descricao {
  white-space: pre-line;
}

Com pre-line, o navegador respeita as quebras do texto, continua juntando espaços repetidos e quebra linhas longas no fim do espaço disponível. Você imprime só o texto escapado, sem nl2br. É útil quando o texto chega por JavaScript: atribua com textContent, que não interpreta HTML, e o CSS cuida das quebras. Evite white-space: pre ou a tag pre para texto comum: elas não quebram linhas longas, e no celular o texto ganha rolagem horizontal.

// JavaScript: seguro e com quebras, graças ao pre-line
document.querySelector('.descricao').textContent = pedido.descricao;

// ERRADO: troca quebras por <br> e injeta HTML do usuário na página
// el.innerHTML = pedido.descricao.replace(/\n/g, '<br>');

Uma convenção por campo

O problema mais caro não é a quebra sumir, é a mistura. Se parte dos registros foi salva já com <br> e parte com quebras puras, a tela ora mostra a tag escrita, ora come a quebra, e ninguém sabe qual função aplicar. Decida por campo: descrição de orçamento é texto puro e passa por escape e nl2br na saída; corpo de artigo é HTML vindo do editor e passa por um sanitizador que permite só as tags aceitas. Não use a mesma função de saída para os dois. O post sobre editor rico no lugar da textarea trata do lado HTML, e o de entidade HTML explica por que o texto não deve ser guardado já escapado.

No e-mail, a mesma regra vale ao contrário. A versão em texto puro usa a descrição original. A versão HTML usa a saída escapada e convertida. Montar o texto puro a partir do HTML, ou o contrário, costuma deixar &amp; e <br> perdidos na mensagem. O guia de formulário de contato que chega mostra o envio completo.

Erros comuns

  • Testar com '\n' entre aspas simples no PHP. Aparece barra e n na tela, e o diagnóstico vai para o lugar errado.
  • Converter quebras antes de escapar. O <br> vira texto visível na página, e alguém "conserta" tirando o escape.
  • Não escapar o texto do usuário. Qualquer HTML digitado no formulário roda no painel de quem lê.
  • Salvar no banco já com <br>. O e-mail em texto puro, o CSV e a API passam a receber tags no meio do conteúdo.
  • Usar pre para preservar as linhas. Linhas longas não quebram e o celular ganha rolagem horizontal.
  • Tratar só \n ou só \r\n. Texto vindo de outro sistema operacional ou do formulário fica com quebras pela metade.

Checklist

  • Cada campo de texto tem convenção definida: texto puro ou HTML.
  • Texto puro é guardado como foi digitado, sem tags.
  • A saída em HTML escapa antes de converter quebras.
  • Um texto com <b>teste</b> aparece literalmente na tela, sem negrito.
  • Parágrafos digitados no formulário aparecem separados na página.
  • O e-mail em texto puro usa o texto original, sem entidades nem tags.
  • Linhas longas quebram no celular sem rolagem horizontal.

Abra o formulário do seu sistema, digite três parágrafos e um <b>teste</b> no meio, envie e veja a tela de quem recebe. Os parágrafos estão separados, e a tag apareceu como texto ou virou negrito?

Avalie este artigo

Referência: https://www.vivaolinux.com.br/topico/PHP/Pular-Linha-PHP