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 — Sviluppatore front-end e redattore Tech presso OneKitly
Performance web · Formati di file
Verificato su 5 fonti
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.
| Costrutto | Output del ripulitore regex | Cosa fa un parser | Tenere o buttare |
|---|---|---|---|
| Link in linea [testo](url) | Testo tenuto, destinazione cancellata | Testo e destinazione entrambi disponibili | Tenere entrambi: il testo, poi l'URL fra parentesi |
| Trattini bassi in un nome di file: rapporto_final_v2.txt | rapportofinalv2.txt | Invariato: i trattini bassi dentro una parola non sono enfasi | Tenere |
| Asterisco come segno di moltiplicazione: 2 * 3 * 4 | 2 3 4 | Invariato: un delimitatore con uno spazio da entrambi i lati non apre nulla | Tenere |
| Blocco di codice recintato | Si mangia un apice inverso del recinto e modifica il codice dentro | Contenuto conservato tale e quale | Buttare il recinto, tenere ogni carattere interno |
| Link per riferimento [testo][rif] | Le parentesi quadre sopravvivono, e anche la riga di definizione | Destinazione risolta dalla definizione che sta altrove | Tenere testo e destinazione, buttare la riga di definizione |
| Tabella a barre verticali | Una fila di parole, con la riga --- che sopravvive come testo | Righe e celle, e solo sotto GFM — CommonMark non ha tabelle | Tenere le celle con un separatore, buttare la riga di allineamento |
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 →Strumenti correlati
Fonti
- CommonMark — CommonMark Specification — emphasis and strong emphasis, code spans, fenced and indented code blocks, link reference definitions
- GitHub — GitHub Flavored Markdown Spec — tables, task list items, strikethrough and autolink extensions
- IETF — RFC 7763 and RFC 7764 — the text/markdown media type and known variants
- WHATWG — HTML Standard — parsing and the text content of elements
- MDN Web Docs — Node.textContent and the difference between text content and rendered text
Hai notato un errore in questo articolo?