Vai al contenuto
OneKitly

Togliere il Markdown: che cosa perde il testo semplice, e che cosa sbaglia una regex

Pubblicato il 03/06/2025 · 13 min di lettura · Strumenti testo e lingua

Daniel Okonkwo

Daniel OkonkwoSviluppatore front-end e redattore Tech presso OneKitly

Performance web · Formati di file

Verificato su 5 fonti

Vedi il profilo
In breve

Il markdown conserva significato nella sua punteggiatura, quindi cancellare la sintassi cancella informazione. Passa un ripulitore ingenuo su un breve rapporto e le perdite sono concrete: [dettaglio completo](https://example.com/t3.pdf) diventa «dettaglio completo» e la destinazione è sparita senza traccia che ci fosse un link; un elenco a due livelli di regioni e canali esce allineato a sinistra, così «Negozio: +4 %» e «Negozio: stabile» non appartengono più ad alcuna regione; e una tabella diventa una fila di parole la cui riga separatrice --- sopravvive come testo letterale. Sotto c'è un secondo problema: il markdown non ha una sola specifica. CommonMark e GitHub Flavored Markdown divergono — una tabella a barre, il ~~barrato~~, un URL nudo e un elenco di attività sono estensioni GFM e restano testo letterale sotto CommonMark — e un ripulitore a base di regex sbaglia per giunta casi che un parser risolve bene. Verificato in Node: un ripulitore ingenuo trasforma rapporto_final_v2.txt in rapportofinalv2.txt e 2 * 3 * 4 in 2 3 4, e modifica l'interno di un blocco di codice recintato, mentre un parser CommonMark lascia i tre intatti. Conserva il testo del link e la sua destinazione fra parentesi, conserva i segni di elenco e il contenuto dei blocchi di codice tale e quale; butta i segni di enfasi e i cancelletti dei titoli.

Un link diventa testo con la destinazione cancellata, un elenco annidato perde la gerarchia, una tabella diventa una fila di parole. Poi la metà tecnica: il markdown non ha una sola specifica, e un ripulitore a base di regex rovina un nome di file, un segno di moltiplicazione e l'interno di un blocco di codice — tutto messo a confronto con un vero parser.

Il markdown conserva significato nella sua punteggiatura

Il markdown sembra testo semplice con qualche decorazione, il che fa sembrare gratuito togliere la decorazione. Non lo è. Tre dei suoi costrutti portano informazione che non esiste in nessun altro punto del documento. Un link contiene due cose, il testo e la destinazione, e solo una è visibile; cancella la sintassi e la destinazione se ne va con essa. Un elenco annidato contiene una relazione — questo canale appartiene a quella regione — codificata interamente nell'indentazione. Una tabella contiene una griglia, e una griglia sono due dimensioni scritte nell'unica dimensione del testo.

Prendi un breve rapporto: un titolo di secondo livello, una frase con un link e un elenco a due livelli di regioni e dei loro canali. Passaci sopra un ripulitore ingenuo — la solita lista di pattern per titoli, enfasi, codice in linea, link ed elenchi puntati — e torna circa un terzo più corto, il che suona come un buon lavoro finché non lo leggi. «Vedi il dettaglio completo per i numeri grezzi» ora non punta da nessuna parte. «Negozio: +4 %», «Online: +11 %» e «Negozio: stabile» stanno a filo con «America del Nord» ed «Europa», alla stessa indentazione, quindi nulla dice a quale regione appartiene ciascun numero — e il documento contiene due righe «Negozio» diverse che ora si contraddicono.

Il markdown non ha una sola specifica

La descrizione originale del 2004 era uno script Perl e una pagina di prosa, non una grammatica, e ogni implementazione successiva ha risolto le ambiguità a modo suo. CommonMark esiste per rimediare: è una specifica precisa con una suite di test. GitHub Flavored Markdown è CommonMark più quattro estensioni, e quelle estensioni sono esattamente i costrutti che si dà per scontato facciano parte del markdown. Passa lo stesso input in un parser con le estensioni GFM accese e spente e la differenza si vede.

Una tabella a barre diventa una tabella vera sotto GFM e resta un paragrafo di barre letterali sotto CommonMark. Due tilde attorno a una parola diventano una cancellatura sotto GFM e restano due tilde sotto CommonMark. Un indirizzo https:// nudo diventa un link sotto GFM e resta testo sotto CommonMark. E un elemento di elenco che comincia con uno spazio o una x fra parentesi quadre diventa una casella sotto GFM e conserva le parentesi letterali sotto CommonMark. Ognuno di questi costrutti sarà riconosciuto o no dal tuo ripulitore, secondo un'impostazione che nessuno ha messo per iscritto.

C'è inoltre un costrutto comune a entrambe le specifiche che quasi ogni ripulitore a regex dimentica: il titolo setext, una riga di testo sottolineata da una fila di segni di uguale o di trattini. Sia CommonMark sia GFM ne fanno un titolo. Un ripulitore la cui unica regola sui titoli riconosce i cancelletti iniziali lascia la fila di segni di uguale nell'output, come una riga di punteggiatura.

Tre cose che una regex sbaglia e un parser azzecca

Primo, il trattino basso in un nome di file. Passa un ripulitore ingenuo su «Apri rapporto_final_v2.txt e poi archivio_2026_t1.csv» e il pattern per l'enfasi con trattino basso se li mangia entrambi: rapportofinalv2.txt e archivio2026t1.csv. Un parser CommonMark lascia la frase esattamente com'era, perché la specifica dice che un trattino basso dentro una parola non apre né chiude enfasi — quella regola esiste proprio perché lo snake_case sopravviva. L'asterisco si comporta qui diversamente dal trattino basso, e già questo è una regola che una regex non sa esprimere.

Secondo, l'asterisco come operatore. «L'area è 2 * larghezza * altezza, quindi 2 * 3 * 4 = 24» esce dal ripulitore ingenuo come «L'area è 2 larghezza altezza, quindi 2 3 4 = 24», con ogni asterisco sparito e uno spazio doppio al suo posto. Il parser non lo tocca, perché le regole di fiancheggiamento di CommonMark dicono che un delimitatore con spazi da entrambi i lati non può né aprire né chiudere enfasi. La regola è precisa, ben documentata e lunga tre frasi — ed è una regola sul contesto, che è ciò che le espressioni regolari per costruzione non sanno vedere.

Terzo, il link per riferimento, la cui destinazione non sta affatto accanto al testo. Scrivi «Vedi [il rapporto completo][rap] per i dettagli» e metti «[rap]: https://example.com/rapporto-2026.pdf» in fondo al documento: un parser unisce i due in un unico link. Il pattern per i link del ripulitore ingenuo si aspetta una parentesi, non trova nulla, e lascia sia le parentesi quadre nella frase sia la riga di definizione a piè di documento, così l'output è peggiore dell'input in due punti insieme. Nulla di ciò si sistema aggiungendo un pattern in più: risolvere un link per riferimento significa tenere uno stato sull'intero documento, che è esattamente ciò che è un parser.

Blocchi di codice: il contenuto deve sopravvivere intatto

Un blocco di codice è l'unico punto di un documento markdown in cui la sintassi markdown non è sintassi markdown. Il suo scopo è contenere caratteri da riprodurre esattamente, e quei caratteri includono abitualmente asterischi, trattini bassi e cancelletti. Dai a un ripulitore ingenuo un blocco Python recintato che contenga def f(*args), una riga di commento che inizia con un cancelletto e un identificatore scritto con trattini bassi: commette tre errori distinti. Si mangia uno dei tre apici inversi del recinto con il suo pattern per il codice in linea, toglie i trattini bassi dall'identificatore, e lascia il resto del recinto nell'output. Un parser segna l'intero blocco come codice e dentro non guarda affatto.

Il blocco di codice indentato è la stessa trappola senza recinto visibile. Quattro spazi a inizio riga creano un blocco di codice, sia in CommonMark sia in GFM: un ripulitore che conosce solo gli apici inversi riscriverà allegramente il contenuto. Un identificatore scritto a_b_c esce come abc, e un commento che inizia con un cancelletto è in balia della regola sui titoli. Se il ripulitore lo scrivi tu, individua prima le zone di codice e mascherale, applica poi tutte le altre regole a ciò che resta, e infine rimetti le zone mascherate tali e quali. È un passaggio in più, ed elimina un'intera classe di guasti.

Che cosa tenere, che cosa buttare

La regola utile non è «togliere la sintassi» ma «togliere la sintassi che portava solo formattazione, e riscrivere quella che portava informazione». Butta i cancelletti dei titoli e le sottolineature setext, i segni di enfasi, i recinti di codice, i segni di citazione e le righe orizzontali: nessuno dice qualcosa che le parole non dicano. Riscrivi il resto. Un link diventa il suo testo seguito dalla sua destinazione fra parentesi. Un segno di elenco diventa un punto elenco, e l'indentazione resta, perché è lì che vive la gerarchia. Una tabella diventa una riga per ciascuna riga con un separatore visibile fra le celle, e la riga di allineamento sparisce. Un'immagine diventa il suo testo alternativo, l'unica parte di essa che sia mai stata parole.

Lo stesso documento trattato così conserva tutto ciò che il passaggio ingenuo aveva perso. Il titolo è ancora una riga di parole, la frase porta ancora la sua destinazione fra parentesi, e l'elenco a due livelli ha ancora due livelli, quindi i quattro numeri appartengono ancora alle due regioni. È più lungo dell'output ingenuo, e quella lunghezza in più è esattamente l'informazione che l'output ingenuo aveva cancellato.

Vale la pena conoscere una scorciatoia, con l'avvertenza che l'accompagna. Se un vero parser markdown è già disponibile, la via corretta più breve verso il testo semplice è rendere il markdown in HTML e poi estrarre il testo da quell'HTML, perché il parser ha già risolto per te i link per riferimento, i blocchi di codice e le regole di fiancheggiamento. L'avvertenza: la seconda metà di quella via è un problema a sé — togliere l'HTML ha un proprio ordine delle operazioni, trattato nell'articolo compagno di questa serie, e farlo con una seconda regex reintroduce esattamente la classe di bug da cui sei appena scampato.

Sei costrutti markdown passati attraverso un ripulitore a base di regex e attraverso un parser CommonMark, entrambi eseguiti in Node 26.3. La colonna del parser è ciò che un lettore si aspetterebbe; la colonna della regex è ciò che produce un elenco di pattern.
CostruttoOutput del ripulitore regexCosa fa un parserTenere o buttare
Link in linea [testo](url)Testo tenuto, destinazione cancellataTesto e destinazione entrambi disponibiliTenere entrambi: il testo, poi l'URL fra parentesi
Trattini bassi in un nome di file: rapporto_final_v2.txtrapportofinalv2.txtInvariato: i trattini bassi dentro una parola non sono enfasiTenere
Asterisco come segno di moltiplicazione: 2 * 3 * 42 3 4Invariato: un delimitatore con uno spazio da entrambi i lati non apre nullaTenere
Blocco di codice recintatoSi mangia un apice inverso del recinto e modifica il codice dentroContenuto conservato tale e qualeButtare il recinto, tenere ogni carattere interno
Link per riferimento [testo][rif]Le parentesi quadre sopravvivono, e anche la riga di definizioneDestinazione risolta dalla definizione che sta altroveTenere testo e destinazione, buttare la riga di definizione
Tabella a barre verticaliUna fila di parole, con la riga --- che sopravvive come testoRighe e celle, e solo sotto GFM — CommonMark non ha tabelleTenere le celle con un separatore, buttare la riga di allineamento
Rimuovi MarkdownRimuovi la formattazione Markdown per ottenere testo semplice.Prova lo strumento

Domande frequenti

Dove finisce l'URL del link quando si toglie il markdown?
Da nessuna parte, nella maggior parte dei ripulitori. Il pattern abituale sostituisce l'intero costrutto [testo](url) con il testo catturato: «Vedi il [dettaglio completo](https://example.com/t3.pdf)» diventa «Vedi il dettaglio completo», una frase che promette una destinazione che non ha più. Se il testo semplice è per una persona, conserva la destinazione fra parentesi dopo il testo; se alimenta un indice di ricerca, conserva il testo e memorizza l'URL in un campo separato. Cancellarlo in silenzio è l'unica opzione senza caso d'uso.
Perché togliendo il markdown i miei nomi di file si sono rovinati?
Perché il ripulitore ha scambiato i trattini bassi per enfasi. Un pattern fatto di un trattino basso, una cattura pigra e un altro trattino basso riconosce il mezzo di rapporto_final_v2.txt e cancella entrambi i trattini, dando rapportofinalv2.txt. CommonMark deliberatamente non lo fa: i trattini bassi dentro una parola non aprono né chiudono mai enfasi, regola che tiene intatti gli identificatori snake_case e i nomi di file. Un ripulitore che sbaglia qui non implementa markdown; implementa un'ipotesi su markdown.
Togliere il markdown è come convertire in HTML e togliere i tag?
È una buona strada, e non una strada identica. Rendere in HTML con un vero parser risolve correttamente i link per riferimento, i blocchi di codice e le regole di enfasi, che è la maggior parte della difficoltà. Ma il secondo passaggio butta via proprio ciò che volevi tenere, a meno che non te ne occupi: l'href di un'ancora, l'alt di un'immagine, i confini di cella di una tabella. Estrai quegli attributi di proposito prima di prendere il contenuto testuale, e ricorda che togliere l'HTML ha un proprio problema di ordine delle operazioni, trattato a parte in questa serie.
Che cosa succede a una tabella?
Sotto un ripulitore ingenuo diventa una fila di parole: le barre verticali diventano spazi e la riga di allineamento di trattini sopravvive come una riga di punteggiatura, così il lettore riceve quattro colonne di numeri senza nulla che dica quale sia quale. Sotto un parser è una griglia che puoi ridisegnare: una riga per ciascuna riga, celle unite da un separatore visibile, riga di allineamento scartata. Nota che una tabella non è nemmeno markdown in senso stretto: è un'estensione GFM, e sotto il solo CommonMark quelle righe di barre sono un paragrafo ordinario.
Il ripulitore deve conoscere le estensioni GFM?
Se i tuoi documenti vengono da un servizio di hosting del codice, da un tracker di problemi o da uno strumento di chat, sì. Tabelle, barrato, autolink su URL nudo ed elenchi di attività sono estensioni GFM, e un ripulitore solo-CommonMark lascia la loro sintassi nell'output come caratteri letterali: due tilde attorno a una parola, parentesi quadre attorno a uno spazio, file di barre. Esiste anche l'errore opposto: applicare le regole GFM a un documento scritto per un renderizzatore CommonMark stretto trasforma un paragrafo di barre in una tabella che il suo autore non ha mai scritto. Scegli il sapore in base alla fonte, e mettilo per iscritto accanto al codice.
I blocchi di codice vanno tenuti o rimossi?
Tieni il loro contenuto e togli solo il recinto. Ciò che non deve mai accadere è l'opzione di mezzo, in cui il recinto sparisce e poi le regole su enfasi, titoli e codice in linea passano sul codice interno: produce un testo che sembra codice senza essere il codice scritto, il che è peggio di entrambi gli estremi. Se il tuo testo semplice alimenta un riassuntore o un indice di ricerca e il codice aggiunge solo rumore, elimina il blocco per intero e lascia un segnaposto al suo posto. Modificare l'interno di un blocco di codice è l'unica cosa senza una lettura difendibile.

Articoli che potrebbero interessarti

Tutte le guide
TutorialPulire un testo disordinato: l'ordine delle operazioni che conta davveroTogliere i tag prima di decodificare le entità, ripulire i bordi prima di deduplicare, comprimere gli spazi per ultimo. Tre ordini eseguiti in Node, una pipeline di nove passaggi nella sequenza giusta e i caratteri invisibili — U+00A0, U+200B, U+FEFF — che sopravvivono a ogni pulizia ingenua.GuidaTogliere l'HTML come si deve: che cosa può e non può fare un rimuovitore di tagTogliere i tag e sanificare l'HTML sono due lavori diversi. Un frammento reale passato attraverso una regex ingenua e attraverso un rimuovitore consapevole della formattazione, con i contenuti di script e style, le interruzioni di blocco, i commenti, i CDATA e l'ordine delle entità mostrati in output.GuidaFormattare i numeri per sei lingue: separatori, valuta e il ritorno al valore1.234,56 e 1,234.56 sono lo stesso numero, e confonderli cambia il valore che un lettore legge. Abbiamo eseguito Intl.NumberFormat per tutte e sei le lingue del sito e stampato ogni separatore — compreso quello invisibile usato dal francese — e poi misurato perché parseFloat non può disfare nulla di tutto ciò.SpiegazioneLe emoji sono più difficili di quanto sembri: perché «basta toglierle» non ha una risposta in una rigaUna emoji visibile può valere un punto di codice o quattordici unità UTF-16. Abbiamo lanciato tre espressioni regolari diffuse su una frase vera e ognuna si è rotta in modo diverso; una ha cancellato le cifre. Ecco perché, quale proprietà Unicode risponde a quale domanda, e la regola a gruppi di grafemi che funziona davvero.GuidaLe liste di attività in Markdown e cosa viene reso davvero e doveLe liste di attività non sono in CommonMark. Sono un'estensione di GitHub Flavored Markdown, ed è per questo che lo stesso file mostra caselle in un posto e parentesi quadre letterali in un altro. La regola esatta del marcatore, cosa fa l'annidamento e una tabella di cosa è CommonMark, cosa è GFM e cosa non è nessuno dei due — verificato su entrambe le specifiche e quattro motori di resa.SpiegazioneContare le parole è ambiguo, e ogni strumento risponde in modo diversoUn conteggio di parole è una definizione, non una misura. Abbiamo contato lo stesso paragrafo in quattro modi ottenendo 25, 28, 33 e 38; poi abbiamo contato 50.000 caratteri di prosa ordinaria ottenendo accordo entro il 4,5 %. Lo scarto dipende interamente da composti, cifre e URL.

Strumenti correlati

Fonti

Hai notato un errore in questo articolo?