Ir para o conteúdo
OneKitly

Retirar o Markdown: o que o texto simples perde, e em que se engana uma regex

Publicado a 03/06/2025 · 13 min de leitura · Ferramentas de texto e idioma

Daniel Okonkwo

Daniel OkonkwoProgramador front-end e redator de Tecnologia na OneKitly

Desempenho web · Formatos de ficheiro

Verificado a partir de 5 fontes

Ver perfil
Em resumo

O markdown guarda significado na sua pontuação, por isso apagar a sintaxe apaga informação. Passe um limpador ingénuo por um relatório curto e as perdas são concretas: [detalhe completo](https://example.com/t3.pdf) torna-se «detalhe completo» e o destino desapareceu sem rasto de que houve uma ligação; uma lista de dois níveis de regiões e canais sai alinhada à esquerda, de modo que «Loja: +4 %» e «Loja: estável» já não pertencem a nenhuma região; e uma tabela torna-se uma fila de palavras cuja linha separadora --- sobrevive como texto literal. Por baixo há um segundo problema: o markdown não tem uma especificação única. O CommonMark e o GitHub Flavored Markdown divergem — uma tabela de barras, o ~~riscado~~, um URL nu e uma lista de tarefas são extensões GFM e continuam texto literal sob CommonMark — e um limpador à base de regex ainda por cima erra em casos que um analisador resolve bem. Verificado no Node: um limpador ingénuo transforma relatorio_final_v2.txt em relatoriofinalv2.txt e 2 * 3 * 4 em 2 3 4, e edita o interior de um bloco de código cercado, enquanto um analisador CommonMark deixa os três intactos. Guarde o texto da ligação e o seu destino entre parênteses, guarde os marcadores de lista e o conteúdo dos blocos de código tal e qual; deite fora as marcas de ênfase e os cardinais de título.

Uma ligação torna-se texto com o destino apagado, uma lista aninhada perde a hierarquia, uma tabela torna-se uma fila de palavras. Depois a metade técnica: o markdown não tem uma especificação única, e um limpador à base de regex estraga um nome de ficheiro, um sinal de multiplicação e o interior de um bloco de código — tudo confrontado com um analisador a sério.

O markdown guarda significado na sua pontuação

O markdown parece texto simples com enfeites, o que faz parecer gratuito retirar os enfeites. Não é. Três das suas construções carregam informação que não existe em mais nenhum sítio do documento. Uma ligação contém duas coisas, o texto e o destino, das quais só uma é visível; apague a sintaxe e o destino vai com ela. Uma lista aninhada contém uma relação — este canal pertence àquela região — codificada inteiramente na indentação. Uma tabela contém uma grelha, e uma grelha são duas dimensões escritas na única dimensão do texto.

Tome um relatório curto: um título de nível dois, uma frase com uma ligação, e uma lista de dois níveis de regiões e dos seus canais. Passe-lhe um limpador ingénuo — a lista habitual de padrões para títulos, ênfase, código em linha, ligações e marcadores — e volta cerca de um terço mais curto, o que soa a trabalho bem feito até se ler. «Ver o detalhe completo para os números brutos» já não aponta para lado nenhum. «Loja: +4 %», «Online: +11 %» e «Loja: estável» ficam colados a «América do Norte» e «Europa», com a mesma indentação, por isso nada diz a que região pertence cada número — e o documento contém duas linhas «Loja» diferentes que agora se contradizem.

O markdown não tem uma especificação única

A descrição original de 2004 era um script em Perl e uma página de prosa, não uma gramática, e cada implementação posterior resolveu as ambiguidades à sua maneira. O CommonMark existe para corrigir isso: é uma especificação precisa com uma bateria de testes. O GitHub Flavored Markdown é CommonMark mais quatro extensões, e essas extensões são precisamente as construções que se supõe fazerem parte do markdown. Passe a mesma entrada por um analisador com as extensões GFM ligadas e desligadas e a diferença vê-se.

Uma tabela de barras torna-se uma tabela real sob GFM e continua um parágrafo de barras literais sob CommonMark. Dois tis à volta de uma palavra tornam-se um apagamento sob GFM e continuam dois tis sob CommonMark. Um endereço https:// nu torna-se uma ligação sob GFM e continua texto sob CommonMark. E um item de lista que começa por um espaço ou um x entre parênteses retos torna-se uma caixa sob GFM e mantém os parênteses literais sob CommonMark. Cada uma destas construções será reconhecida ou não pelo seu limpador, conforme uma definição que ninguém escreveu.

Há ainda uma construção comum às duas especificações que quase todos os limpadores à base de regex esquecem: o título setext, uma linha de texto sublinhada por uma fila de sinais de igual ou de hífenes. Tanto o CommonMark como o GFM a transformam num título. Um limpador cuja única regra de título reconhece os cardinais iniciais deixa a fila de sinais de igual na saída, como uma linha de pontuação.

Três coisas que uma regex falha e um analisador acerta

Primeiro, o sublinhado num nome de ficheiro. Passe um limpador ingénuo por «Abre relatorio_final_v2.txt e depois arquivo_2026_t1.csv» e o padrão de ênfase por sublinhado come os dois: relatoriofinalv2.txt e arquivo2026t1.csv. Um analisador CommonMark deixa a frase exatamente igual, porque a especificação diz que um sublinhado dentro de uma palavra não abre nem fecha ênfase — essa regra existe precisamente para que o snake_case sobreviva. O asterisco comporta-se aqui de modo diferente do sublinhado, o que já é uma regra que uma regex não sabe exprimir.

Segundo, o asterisco como operador. «A área é 2 * largura * altura, portanto 2 * 3 * 4 = 24» sai do limpador ingénuo como «A área é 2 largura altura, portanto 2 3 4 = 24», com todos os asteriscos desaparecidos e um espaço duplo no lugar. O analisador não lhe toca, porque as regras de flanqueamento do CommonMark dizem que um delimitador com espaços dos dois lados não pode abrir nem fechar ênfase. A regra é precisa, bem documentada e cabe em três frases — e é uma regra de contexto, que é justamente o que as expressões regulares não conseguem ver por construção.

Terceiro, a ligação por referência, cujo destino não está de todo ao lado do texto. Escreva «Ver [o relatório completo][rel] para detalhes» e ponha «[rel]: https://example.com/relatorio-2026.pdf» no fim do documento: um analisador junta os dois numa só ligação. O padrão de ligação do limpador ingénuo espera um parêntesis, não encontra nada, e deixa tanto os parênteses retos na frase como a linha de definição ao fundo do documento, por isso a saída é pior do que a entrada em dois sítios ao mesmo tempo. Nada disso se resolve acrescentando mais um padrão: resolver uma ligação por referência exige manter estado à escala do documento, que é exatamente o que um analisador é.

Blocos de código: o conteúdo tem de sobreviver intacto

Um bloco de código é o único sítio de um documento markdown onde a sintaxe markdown não é sintaxe markdown. A sua razão de ser é conter caracteres que têm de ser reproduzidos exatamente, e esses caracteres incluem habitualmente asteriscos, sublinhados e cardinais. Dê a um limpador ingénuo um bloco de Python cercado que contenha def f(*args), uma linha de comentário começada por cardinal e um identificador escrito com sublinhados: comete três erros distintos. Come uma das três plicas invertidas da cerca com o seu padrão de código em linha, retira os sublinhados do identificador, e deixa o resto da cerca na saída. Um analisador marca o bloco inteiro como código e não olha lá para dentro.

O bloco de código indentado é a mesma armadilha sem cerca visível. Quatro espaços no início da linha criam um bloco de código, tanto em CommonMark como em GFM: um limpador que só conhece as plicas invertidas reescreverá alegremente o conteúdo. Um identificador escrito a_b_c sai como abc, e um comentário começado por cardinal fica à mercê da regra dos títulos. Se escrever o limpador, detete primeiro as zonas de código e mascare-as, aplique depois todas as outras regras ao que resta, e depois reponha as zonas mascaradas tal e qual. É mais uma passagem, e elimina uma classe inteira de falhas.

O que guardar, o que deitar fora

A regra útil não é «retirar a sintaxe» mas «retirar a sintaxe que só carregava formatação, e reescrever a que carregava informação». Deite fora os cardinais de título e os sublinhados setext, as marcas de ênfase, as cercas de código, os sinais de citação e as réguas horizontais: nenhum diz nada que as palavras não digam. Reescreva o resto. Uma ligação torna-se o seu texto seguido do seu destino entre parênteses. Um marcador torna-se um caráter de marcador, e a indentação fica, porque é aí que vive a hierarquia. Uma tabela torna-se uma linha por fila com um separador visível entre células, e a linha de alinhamento desaparece. Uma imagem torna-se o seu texto alternativo, a única parte dela que alguma vez foram palavras.

O mesmo documento tratado assim conserva tudo o que a passagem ingénua tinha perdido. O título continua a ser uma linha de palavras, a frase continua a levar o seu destino entre parênteses, e a lista de dois níveis continua a ter dois, por isso os quatro números continuam a pertencer às duas regiões. É mais longo do que a saída ingénua, e esse comprimento extra é exatamente a informação que a saída ingénua apagou.

Vale a pena conhecer um atalho, com o aviso que o acompanha. Se já houver um analisador markdown a sério disponível, a rota correta mais curta para o texto simples é renderizar o markdown em HTML e depois extrair o texto desse HTML, porque o analisador já resolveu por si as ligações por referência, os blocos de código e as regras de flanqueamento. O aviso: a segunda metade dessa rota é um problema por si só — retirar HTML tem a sua própria ordem de operações, tratada no artigo companheiro desta série, e fazê-lo com uma segunda regex reintroduz exatamente a classe de erro de que acabou de escapar.

Seis construções markdown passadas por um limpador à base de regex e por um analisador CommonMark, ambos executados no Node 26.3. A coluna do analisador é o que um leitor esperaria; a coluna da regex é o que uma lista de padrões produz.
ConstruçãoSaída do limpador regexO que faz um analisadorGuardar ou deitar fora
Ligação em linha [texto](url)Texto guardado, destino apagadoTexto e destino ambos disponíveisGuardar ambos: o texto e depois o URL entre parênteses
Sublinhados num nome de ficheiro: relatorio_final_v2.txtrelatoriofinalv2.txtSem alterações: os sublinhados dentro de uma palavra não são ênfaseGuardar
Asterisco como sinal de multiplicação: 2 * 3 * 42 3 4Sem alterações: um delimitador com espaço dos dois lados não abre nadaGuardar
Bloco de código cercadoCome uma plica invertida da cerca e edita o código de dentroConteúdo conservado tal e qualDeitar fora a cerca, guardar cada caráter de dentro
Ligação por referência [texto][ref]Os parênteses retos sobrevivem e a linha de definição tambémDestino resolvido a partir da definição noutro sítioGuardar texto e destino, deitar fora a linha de definição
Tabela de barras verticaisUma fila de palavras, com a linha --- a sobreviver como textoLinhas e células, e só sob GFM — o CommonMark não tem tabelasGuardar as células com um separador, deitar fora a linha de alinhamento
Remover MarkdownRemova a formatação Markdown para obter texto simples.Experimentar a ferramenta

Perguntas frequentes

Para onde vai o URL da ligação quando se retira o markdown?
Para lado nenhum, na maioria dos limpadores. O padrão habitual substitui toda a construção [texto](url) pelo texto capturado: «Ver o [detalhe completo](https://example.com/t3.pdf)» torna-se «Ver o detalhe completo», uma frase que promete um destino que já não tem. Se o texto simples é para uma pessoa, guarde o destino entre parênteses depois do texto; se alimenta um índice de pesquisa, guarde o texto e armazene o URL num campo à parte. Apagá-lo em silêncio é a única opção sem caso de uso.
Porque é que ao retirar o markdown os meus nomes de ficheiro ficaram estragados?
Porque o limpador tomou os sublinhados por ênfase. Um padrão feito de um sublinhado, uma captura preguiçosa e outro sublinhado reconhece o meio de relatorio_final_v2.txt e apaga ambos os sublinhados, dando relatoriofinalv2.txt. O CommonMark evita isso de propósito: um sublinhado dentro de uma palavra nunca abre nem fecha ênfase, regra que mantém intactos os identificadores snake_case e os nomes de ficheiro. Um limpador que erra aqui não implementa markdown; implementa um palpite sobre markdown.
Retirar o markdown é o mesmo que converter para HTML e retirar as etiquetas?
É uma boa rota, e não uma rota idêntica. Renderizar para HTML com um analisador a sério resolve corretamente as ligações por referência, os blocos de código e as regras de ênfase, que é a maior parte da dificuldade. Mas o segundo passo deita fora exatamente o que queria guardar, a não ser que trate disso: o href de uma âncora, o alt de uma imagem, as fronteiras de célula de uma tabela. Extraia esses atributos de propósito antes de tomar o conteúdo textual, e lembre-se de que retirar HTML tem o seu próprio problema de ordem das operações, tratado à parte nesta série.
O que acontece a uma tabela?
Sob um limpador ingénuo torna-se uma fila de palavras: as barras verticais tornam-se espaços e a linha de alinhamento de hífenes sobrevive como uma linha de pontuação, de modo que o leitor recebe quatro colunas de números sem nada que diga qual é qual. Sob um analisador é uma grelha que pode voltar a desenhar: uma linha por fila, células unidas por um separador visível, linha de alinhamento descartada. Repare que uma tabela nem sequer é markdown em sentido estrito: é uma extensão GFM, e sob CommonMark simples essas linhas de barras são um parágrafo comum.
O limpador precisa de conhecer as extensões GFM?
Se os seus documentos vêm de um alojamento de código, de um gestor de incidências ou de uma ferramenta de conversa, sim. As tabelas, o riscado, as ligações automáticas sobre URL nu e as listas de tarefas são extensões GFM, e um limpador só-CommonMark deixa a sintaxe deles na saída como caracteres literais: dois tis à volta de uma palavra, parênteses retos à volta de um espaço, filas de barras. O erro inverso também existe: aplicar regras GFM a um documento escrito para um renderizador CommonMark estrito transforma um parágrafo de barras numa tabela que o seu autor nunca escreveu. Escolha o sabor conforme a fonte, e deixe-o escrito ao lado do código.
Devem os blocos de código ser guardados ou retirados?
Guarde o conteúdo deles e retire apenas a cerca. O que nunca deve acontecer é a opção do meio, em que a cerca desaparece e depois as regras de ênfase, títulos e código em linha passam sobre o código de dentro: isso produz um texto que parece código sem ser o código que foi escrito, o que é pior do que qualquer dos extremos. Se o seu texto simples alimenta um resumidor ou um índice de pesquisa e o código só acrescenta ruído, elimine o bloco inteiro e deixe uma marca no lugar dele. Editar o interior de um bloco de código é a única coisa sem leitura defensável.

Artigos que podem interessar-lhe

Todos os guias
TutorialLimpar texto desarrumado: a ordem das operações que realmente importaRetirar etiquetas antes de descodificar entidades, aparar antes de desduplicar, colapsar os espaços no fim. Três ordens executadas no Node, um pipeline de nove passos na sequência certa e os caracteres invisíveis — U+00A0, U+200B, U+FEFF — que sobrevivem a qualquer limpeza ingénua.GuiaRetirar HTML com critério: o que um removedor de etiquetas pode e não pode fazerRetirar etiquetas e sanear HTML são dois trabalhos diferentes. Um fragmento real passado por uma regex ingénua e por um limpador consciente da formatação, com o conteúdo de script e style, os cortes de bloco, os comentários, os CDATA e a ordem das entidades mostrados na saída.GuiaFormatar números para seis línguas: separadores, moeda e o regresso ao valor1 234,56 e 1,234.56 são o mesmo número, e confundi-los muda o valor que um leitor lê. Corremos Intl.NumberFormat para as seis localizações do site e imprimimos cada separador — incluindo o invisível que o francês usa — e depois medimos porque parseFloat não consegue desfazer nada disso.ExplicaçãoOs emoji são mais difíceis do que parecem: porque «basta retirá-los» não tem resposta numa linhaUm emoji visível pode valer um ponto de código ou catorze unidades UTF-16. Corremos três expressões regulares populares sobre uma frase real e cada uma falhou de maneira diferente; uma apagou os algarismos. Eis porquê, que propriedade Unicode responde a que pergunta, e a regra de grupos de grafemas que funciona mesmo.GuiaAs listas de tarefas em Markdown e o que se apresenta realmente em cada sítioAs listas de tarefas não estão no CommonMark. São uma extensão do GitHub Flavored Markdown, e por isso o mesmo ficheiro mostra caixas num sítio e parênteses retos literais noutro. A regra exata do marcador, o que faz o aninhamento e uma tabela do que é CommonMark, do que é GFM e do que não é nenhum — verificado contra ambas as especificações e quatro motores de apresentação.ExplicaçãoContar palavras é ambíguo, e cada ferramenta responde de forma diferenteUma contagem de palavras é uma definição, não uma medição. Contámos o mesmo parágrafo de quatro maneiras e obtivemos 25, 28, 33 e 38; depois contámos 50 000 caracteres de prosa vulgar e obtivemos acordo dentro de 4,5 %. A diferença deve-se inteiramente a compostos, algarismos e URL.

Ferramentas relacionadas

Fontes

Detetaste um erro neste artigo?