I caratteri Unicode invisibili non occupano spazio visibile ma fanno comunque parte della stringa. I più comuni sono gli spazi a larghezza zero, i trattini morbidi, i byte-order mark, gli spazi non separabili e i segni direzionali. Ognuno ha uno scopo tipografico legittimo, e ognuno compromette ricerca, convalida, parsing e deduplicazione quando sopravvive a un copia-incolla.
I caratteri che incontrerai davvero
| Carattere | Code point | Scopo | Cosa compromette |
|---|---|---|---|
| Spazio a larghezza zero | U+200B | Suggerisce un punto di interruzione di riga | Ricerca, confronto tra stringhe |
| Congiungitore zero-width negativo | U+200C | Impedisce l’unione delle lettere | Legature, testo arabo e indiano |
| Congiungitore zero-width | U+200D | Forza l’unione delle lettere | Sequenze emoji, lunghezza del testo |
| Trattino morbido | U+00AD | Trattino visibile solo a fine riga | Ricerca, testo copiato |
| Spazio non separabile | U+00A0 | Spazio che non va mai a capo | Trim, parsing CSV, codice |
| Spazio sottile non separabile | U+202F | Spazio sottile prima della punteggiatura | Come sopra |
| Byte-order mark | U+FEFF | Segna la codifica all’inizio del file | Prima colonna CSV, parsing JSON |
| Word joiner | U+2060 | Impedisce l’interruzione, senza larghezza | Ricerca |
| Segno sinistra-destra | U+200E | Imposta la direzione del testo | Ordine di visualizzazione, trim |
| Segno destra-sinistra | U+200F | Imposta la direzione del testo | Ordine di visualizzazione |
| Override RTL | U+202E | Forza la visualizzazione da destra a sinistra | Falsificazione dei nomi file |
| Separatore di riga | U+2028 | Interruzione di riga Unicode | Stringhe letterali JavaScript |
| Separatore di paragrafo | U+2029 | Interruzione di paragrafo Unicode | JSON nei parser più vecchi |
Ognuno di questi caratteri ha un compito reale. Il problema non è mai la loro esistenza: è che viaggiano in silenzio tra sistemi che li trattano in modo diverso.
Da dove arrivano
- Copia da pagine web. I siti inseriscono spazi a larghezza zero per controllare dove vanno a capo le stringhe lunghe, e trattini morbidi per il testo giustificato. Entrambi vengono copiati insieme al testo.
- Word processor. Gli spazi non separabili vengono inseriti automaticamente per tenere un numero con la sua unità di misura, o un nome su una sola riga.
- Estrazione da PDF. Copiando da un PDF compaiono spesso trattini morbidi nei punti in cui il tipografo ha spezzato le parole tra le righe, oltre a spazi non separabili dovuti alla giustificazione.
- Interfacce di chat IA. L’output viene renderizzato come HTML, quindi la copia porta con sé i caratteri di spaziatura usati dalla formattazione, di solito U+00A0 e U+202F.
- Conversioni di codifica. Un byte-order mark compare quando un file viene salvato come UTF-8 con BOM, cosa comune su Windows.
- Inserimento deliberato. I caratteri a larghezza zero sono stati usati per marcare i documenti e rintracciare le fughe di notizie, e per far passare testo oltre i filtri per parole chiave.
Lo schema è sempre lo stesso: entrano quando il testo passa da un contesto di presentazione a un contesto di dati.
Come creano problemi
La ricerca fallisce in silenzio. Uno spazio a larghezza zero dentro una parola significa che cercarla non restituisce nulla. Nulla indica il perché: la parola è visibilmente lì.
La convalida rifiuta input corretti. Un indirizzo email con uno spazio non separabile finale non supera il controllo di formato. L’utente vede un indirizzo corretto e un messaggio di errore senza senso.
I confronti falliscono. "Ada" === "Ada\u200B" è falso. La deduplicazione ignora duplicati evidenti; le ricerche non restituiscono nulla; i join perdono righe.
Il trim non aiuta. La maggior parte delle implementazioni di trim() rimuove solo gli spazi ASCII. Uno spazio non separabile sopravvive a .trim() in molti linguaggi: ecco perché «l’ho già ripulito con trim» è un vicolo cieco così frequente.
Il codice non compila. Uno spazio non separabile al posto di uno spazio normale produce un errore di sintassi su una riga che sembra perfetta.
Le importazioni CSV si corrompono. Un byte-order mark si attacca alla prima intestazione, quindi id diventa \uFEFFid e la prima colonna non viene mappata, senza alcun avviso.
La lunghezza è sbagliata. I congiungitori zero-width nelle sequenze emoji fanno sì che una singola emoji visibile sia composta da più code point, quindi i limiti di caratteri rifiutano input che sembrano ampiamente nei limiti.
I nomi dei file possono ingannare. U+202E inverte il testo visualizzato, quindi report\u202Egnp.exe può apparire come reportexe.png. È un vero problema di sicurezza, non una curiosità.
Come trovarli e rimuoverli
Prima analizza. Incolla il testo nello scanner di caratteri invisibili. Indica quali tipi sono presenti e in che quantità, poi li rimuove su richiesta. Vedere prima i conteggi ti dice se hai a che fare con un singolo carattere perso o con una contaminazione sistematica.
In un editor. VS Code evidenzia di default la maggior parte dei caratteri invisibili tramite l’impostazione unicode-highlight. Per un trova-e-sostituisci con regex, questo pattern cattura l’insieme più comune:
[\u200B-\u200F\u00AD\uFEFF\u2060\u202A-\u202E\u2028\u2029]
Nel codice, normalizza al confine: quando i dati arrivano, non quando vengono usati:
const clean = input
.replace(/[\u200B-\u200F\u2060\uFEFF\u00AD\u202A-\u202E]/g, "")
.replace(/[\u00A0\u202F]/g, " ")
.normalize("NFC")
.trim();
Nota il trattamento in due passaggi: i caratteri a larghezza zero vengono eliminati, mentre gli spazi non separabili vengono convertiti in spazi normali: eliminarli unirebbe le parole.
Non rimuovere alla cieca. I congiungitori zero-width sono strutturali nelle sequenze emoji e nelle scritture araba, persiana e in molte scritture indiane, dove rimuoverli cambia il modo in cui le parole vengono renderizzate. Rimuovi in modo aggressivo in identificatori, chiavi e codice; con cautela nei contenuti rivolti agli utenti.
Per la questione specifica se gli strumenti IA inseriscano questi caratteri di proposito, vedi ChatGPT nasconde caratteri invisibili?. L’equivalente visibile ma altrettanto problematico è trattato in perché le virgolette curve rompono il codice.
Domande frequenti
Cos’è uno spazio a larghezza zero?
È U+200B, un carattere che non occupa larghezza visibile ma fa comunque parte della stringa. I siti web lo usano per suggerire dove una parola lunga può andare a capo. Compromette ricerca e confronto tra stringhe perché il testo sembra identico mentre i byte sottostanti sono diversi.
Perché la ricerca fallisce su un testo che è chiaramente presente?
Quasi sicuramente c’è un carattere invisibile dentro la parola, di solito uno spazio a larghezza zero o un trattino morbido raccolto da una pagina web o da un PDF. Il testo visualizzato corrisponde a ciò che hai digitato, ma la stringa sottostante no.
trim() rimuove gli spazi non separabili?
Di solito no. La maggior parte delle implementazioni di trim rimuove solo gli spazi ASCII, quindi uno spazio non separabile (U+00A0) sopravvive. Ecco perché un input che sembra non avere spazi iniziali o finali fallisce comunque la convalida dopo il trim.
Come rimuovo i caratteri invisibili dal testo?
Incollalo in uno scanner che li elenca e li rimuove, oppure usa una regex che copra U+200B–U+200F, U+00AD, U+FEFF, U+2060 e U+202A–U+202E. Elimina i caratteri a larghezza zero, ma converti gli spazi non separabili in spazi normali invece di eliminarli, altrimenti le parole si uniranno.
I caratteri invisibili sono mai utili?
Sì. I trattini morbidi controllano le cesure nel testo giustificato, gli spazi non separabili tengono i valori uniti alle unità di misura, e i congiungitori zero-width sono essenziali per le sequenze emoji e per le scritture araba, persiana e indiane. Rimuovili in modo aggressivo nel codice e negli identificatori, con cautela nella prosa.