2009-10-06 12 views
18

Sto utilizzando PHP per gestire il testo da una varietà di fonti. Non prevedo che sarà diverso da UTF-8, ISO-8859-1 o forse WINDOWS-1252. Se si tratta di qualcosa di diverso da uno di questi, devo solo assicurarmi che il testo venga trasformato in una stringa UTF-8 valida, anche se i caratteri vengono persi. L'opzione // TRANSLIT di iconv risolve questo problema? Ad esempio, questo codice garantisce che una stringa sia sicura da inserire in un documento (o database) con codifica UTF-8?Assicurazione di utf-8 valido in PHP

function make_safe_for_utf8_use($string) { 

    $encoding = mb_detect_encoding($string, "UTF-8,ISO-8859-1,WINDOWS-1252"); 

    if ($encoding != 'UTF-8') { 
     return iconv($encoding, 'UTF-8//TRANSLIT', $string); 
    } else { 
     return $string; 
    } 
} 

risposta

32

UTF-8 può memorizzare qualsiasi carattere Unicode. Se la tua codifica è qualcos'altro, incluso ISO-8859-1 o Windows-1252, UTF-8 può memorizzare ogni carattere in esso. Quindi non devi preoccuparti di perdere alcun carattere quando converti una stringa da qualsiasi altra codifica in UTF-8.

Inoltre, sia ISO-8859-1 sia Windows-1252 sono codifiche a byte singolo in cui ogni byte è valido. Non è tecnicamente possibile distinguere tra loro. Avrei scelto Windows-1252 come corrispondenza predefinita per le sequenze non UTF-8, poiché gli unici byte che decodificano in modo diverso sono l'intervallo 0x80-0x9F. Questi decodificano vari caratteri come le virgolette intelligenti e l'Euro in Windows-1252, mentre in ISO-8859-1 sono caratteri di controllo invisibili che non vengono quasi mai utilizzati. I browser Web a volte possono dire che stanno usando ISO-8859-1, ma spesso useranno davvero Windows-1252.

sarebbe questo codice verificare che una stringa è sicuro da inserire in un documento UTF-8 codificato

Si sarebbe certamente desidera impostare il parametro opzionale ‘stretta’ a TRUE per questo scopo. Ma non sono sicuro che questo copra tutte le sequenze UTF-8 non valide. La funzione non pretende di controllare esplicitamente una sequenza di byte per la validità di UTF-8. Esistono casi noti in cui mb_detect_encoding potrebbe indovinare UTF-8 in modo errato prima, anche se non so se ciò può ancora accadere in modalità rigorosa.

Se si vuole essere sicuri, fai da te utilizzando il W3-recommended regex:

if (preg_match('%^(?: 
     [\x09\x0A\x0D\x20-\x7E]   # ASCII 
    | [\xC2-\xDF][\x80-\xBF]    # non-overlong 2-byte 
    | \xE0[\xA0-\xBF][\x80-\xBF]   # excluding overlongs 
    | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte 
    | \xED[\x80-\x9F][\x80-\xBF]   # excluding surrogates 
    | \xF0[\x90-\xBF][\x80-\xBF]{2}  # planes 1-3 
    | [\xF1-\xF3][\x80-\xBF]{3}   # planes 4-15 
    | \xF4[\x80-\x8F][\x80-\xBF]{2}  # plane 16 
)*$%xs', $string)) 
    return $string; 
else 
    return iconv('CP1252', 'UTF-8', $string); 
+0

Grazie mille. So che gli sviluppatori commentano sempre la lentezza delle regex: quanto dovrei stare attento a usare questo in grandi loop con molto testo? Ad esempio, un ciclo che itera 200 volte e pulisce il testo di 10.000 caratteri per ogni iterazione. – Brian

+0

Anche se non sono un fan delle espressioni regolari, in questo caso non dovrebbe essere così male. Il regex rallenta quando si hanno sequenze successive 'n'?/'* /' + 'Che possono indurre a tornare indietro alla ricerca di modi diversi di corrispondere. Questo non accadrà in questo caso. – bobince

+0

Eccellente. Pertanto, quando si utilizza iconv come descritto sopra, se si specifica CP1252 come set di caratteri di input e la stringa è diversa da CP1252 o ISO-8859-1, restituirà una stringa sicura UTF-8, sebbene alcuni caratteri potrebbero andare persi. È corretto? – Brian

-1

Non sono sicuro se questo sarebbe ottenere la stessa cosa, ma non poteva basta usare utf8_encode() su tutto il testo, senza preoccuparsi di rilevamento? Se il testo è già UTF-8, non lo farà male. E se non lo è, sarà convertito. Se hai già pensato di farlo, c'è una ragione per cui questo non funzionerebbe per te?

+3

utf8_encode non è idempotente per sequenze di byte che sono già UTF-8. Invece li converte in UTF-8 come se fossero precedentemente ISO-8859-1; quindi otterrai eg. 'Α' invece di 'α'. – bobince

12

Con mbstring libreria, si hanno mb_check_encoding().

Esempio di utilizzo:

mb_check_encoding($string, 'UTF-8'); 

quando la performance, questo è più veloce rispetto alla regex fornite nella risposta accettata.

Un test rapido su miei spettacoli di configurazione (per 20.000 iterazioni):

  • regex: ~ 310ms
  • mb_check_encoding: ~ 90ms

EDIT

Con PHP 7.1.9 su un recente sistema di Windows 10, l'espressione regolare soluzione supera mb_check_encoding() per tutta la lunghezza della stringa (ancora 20.000 iterazioni):

  • 10 chars: regex => 4 ms, mb_check_encoding() => 64ms
  • 10000 caratteri: regex => 125ms, mb_check_encoding() => 2,4 s
+0

Il tuo sistema deve essere urlato velocemente, perché ottengo ~ 5 secondi su 7500 iterazioni su un sistema piuttosto moderno (anche se ho a che fare con stringhe piuttosto grandi, penso all'HTML di un sito web abbastanza moderno). –

3

Solo una nota: Invece di utilizzare la spesso raccomandata (piuttosto complesso) regular expression by W3C, si può semplicemente utilizzare il 'u' modificatore per testare una stringa per Validità UTF-8:

<?php 
    if (preg_match("//u", $string)) { 
     // $string is valid UTF-8 
    } 
+0

anche nei giorni: [Come rilevare se applicare o decodificare utf8 su una stringa?] (http: // stackoverflow .com/a/4407996/367456) – hakre

+0

Semplice controllo della cassa comune, ma non del tutto affidabile.Il comportamento dipende dalla versione di PHP, ma soprattutto, consente sequenze multibyte non valide. http://www.phpwact.org/php/ i18n/charsets # checking_utf-8_for_well_formedness –

0

risposta alla domanda "iconv è idempotente"

nessuno dei due è iconv - iconv non è idempotente

una grande differenza tra utf8_encode() & iconv() è che iconv può sollevare errori come questo " rilevato un carattere incompleto multibyte nella stringa di input" anche con

iconv ('ISO-8859-1', 'UTF-8'. '// IGNORE', $ str)

nel codice precedente:

$ encoding = mb_detect_encoding ($ stringa, "UTF-8, ISO-8859-1, WINDOWS-1252");

dovete sapere mb_detect_encoding può rispondere UFT-8, anche per le stringhe non valide utf8 (utf8 mal formata)