web-dev-qa-db-fra.com

Supprimer les caractères non-utf8 de la chaîne

J'ai du mal à supprimer les caractères non-utf8 de la chaîne, qui ne s'affichent pas correctement. Les caractères sont comme ceci 0x97 0x61 0x6C 0x6F (représentation hexadécimale)

Quel est le meilleur moyen de les supprimer? Expression régulière ou autre chose?

96
Dan Sosedoff

En utilisant une approche regex:

$regex = <<<'END'
/
  (
    (?: [\x00-\x7F]                 # single-byte sequences   0xxxxxxx
    |   [\xC0-\xDF][\x80-\xBF]      # double-byte sequences   110xxxxx 10xxxxxx
    |   [\xE0-\xEF][\x80-\xBF]{2}   # triple-byte sequences   1110xxxx 10xxxxxx * 2
    |   [\xF0-\xF7][\x80-\xBF]{3}   # quadruple-byte sequence 11110xxx 10xxxxxx * 3 
    ){1,100}                        # ...one or more times
  )
| .                                 # anything else
/x
END;
preg_replace($regex, '$1', $text);

Il recherche les séquences UTF-8 et les capture dans le groupe 1. Il correspond également à des octets simples qui n'ont pas pu être identifiés comme faisant partie d'une séquence UTF-8, mais ne les capture pas. Le remplacement est ce qui a été capturé dans le groupe 1. Ceci supprime effectivement tous les octets non valides.

Il est possible de réparer la chaîne en codant les octets non valides sous forme de caractères UTF-8. Mais si les erreurs sont aléatoires, cela pourrait laisser des symboles étranges.

$regex = <<<'END'
/
  (
    (?: [\x00-\x7F]               # single-byte sequences   0xxxxxxx
    |   [\xC0-\xDF][\x80-\xBF]    # double-byte sequences   110xxxxx 10xxxxxx
    |   [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences   1110xxxx 10xxxxxx * 2
    |   [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3 
    ){1,100}                      # ...one or more times
  )
| ( [\x80-\xBF] )                 # invalid byte in range 10000000 - 10111111
| ( [\xC0-\xFF] )                 # invalid byte in range 11000000 - 11111111
/x
END;
function utf8replacer($captures) {
  if ($captures[1] != "") {
    // Valid byte sequence. Return unmodified.
    return $captures[1];
  }
  elseif ($captures[2] != "") {
    // Invalid byte of the form 10xxxxxx.
    // Encode as 11000010 10xxxxxx.
    return "\xC2".$captures[2];
  }
  else {
    // Invalid byte of the form 11xxxxxx.
    // Encode as 11000011 10xxxxxx.
    return "\xC3".chr(ord($captures[3])-64);
  }
}
preg_replace_callback($regex, "utf8replacer", $text);

EDIT:

  • !empty(x) correspondra à des valeurs non vides ("0" est considéré comme vide).
  • x != "" Correspondra à des valeurs non vides, y compris "0".
  • x !== "" Correspondra à tout sauf à "".

x != "" Semble être le meilleur à utiliser dans ce cas.

J'ai aussi un peu accéléré le match. Au lieu de faire correspondre chaque caractère séparément, il correspond à des séquences de caractères UTF-8 valides.

76
Markus Jarderot

Si vous appliquez utf8_encode() à une chaîne déjà en UTF8, une sortie UTF8 tronquée sera renvoyée.

J'ai créé une fonction qui aborde tous ces problèmes. Cela s'appelle Encoding::toUTF8().

Vous n'avez pas besoin de savoir quel est l'encodage de vos chaînes. Il peut s'agir de Latin1 (ISO8859-1), Windows-1252 ou UTF8, ou la chaîne peut en contenir plusieurs. Encoding::toUTF8() convertira tout en UTF8.

Je l'ai fait parce qu'un service me donnait un flux de données tout mélangé, en mélangeant ces encodages dans la même chaîne.

Usage:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::toUTF8($mixed_string);

$latin1_string = Encoding::toLatin1($mixed_string);

J'ai inclus une autre fonction, Encoding :: fixUTF8 (), qui corrigera chaque chaîne UTF8 qui semble tronquée après avoir été encodée plusieurs fois dans UTF8.

Usage:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::fixUTF8($garbled_utf8_string);

Exemples:

echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");

affichera:

Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football

Télécharger:

https://github.com/neitanod/forceutf8

122
Sebastián Grignoli

Vous pouvez utiliser mbstring:

$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');

... supprimera les caractères non valides.

Voir: Le remplacement des caractères UTF-8 non valides par des points d'interrogation, mbstring.substitute_character semble ignoré

56
Frosty Z

Cette fonction supprime tous les caractères NON ASCII, c'est utile mais cela ne résout pas la question:
C’est ma fonction qui fonctionne toujours, quel que soit le codage:

function remove_bs($Str) {  
  $StrArr = str_split($Str); $NewStr = '';
  foreach ($StrArr as $Char) {    
    $CharNo = ord($Char);
    if ($CharNo == 163) { $NewStr .= $Char; continue; } // keep £ 
    if ($CharNo > 31 && $CharNo < 127) {
      $NewStr .= $Char;    
    }
  }  
  return $NewStr;
}

Comment ça fonctionne:

echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you?
19
David D
$text = iconv("UTF-8", "UTF-8//IGNORE", $text);

C'est ce que j'utilise. Semble travailler assez bien. Extrait de http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/

12
Znarkus

essaye ça:

$string = iconv("UTF-8","UTF-8//IGNORE",$string);

Selon le iconv manual , la fonction prendra le premier paramètre comme jeu de caractères en entrée, le deuxième paramètre comme jeu de caractères en sortie et le troisième comme chaîne d'entrée réelle.

Si vous définissez le jeu de caractères d'entrée et de sortie sur UTF-8 et ajoutez le //IGNORE sur le jeu de caractères de sortie, la fonction supprime (supprime) tous les caractères de la chaîne d'entrée qui ne peuvent pas être représentés par le jeu de caractères de sortie. Ainsi, filtrer la chaîne d'entrée en vigueur.

12
technoarya

Le texte peut contenir un caractère non-utf8 . Essayez de faire en premier:

$nonutf8 = mb_convert_encoding($nonutf8 , 'UTF-8', 'UTF-8');

Vous pouvez en savoir plus à ce sujet ici: http://php.net/manual/en/function.mb-convert-encoding.phpnews

7
HTML5 developer

J'ai créé une fonction qui supprime les caractères UTF-8 non valides d'une chaîne. Je l'utilise pour clarifier la description de 27 000 produits avant de générer le fichier d'exportation XML.

public function stripInvalidXml($value) {
    $ret = "";
    $current;
    if (empty($value)) {
        return $ret;
    }
    $length = strlen($value);
    for ($i=0; $i < $length; $i++) {
        $current = ord($value{$i});
        if (($current == 0x9) || ($current == 0xA) || ($current == 0xD) || (($current >= 0x20) && ($current <= 0xD7FF)) || (($current >= 0xE000) && ($current <= 0xFFFD)) || (($current >= 0x10000) && ($current <= 0x10FFFF))) {
                $ret .= chr($current);
        }
        else {
            $ret .= "";
        }
    }
    return $ret;
}
6
mumin

UConverter peut être utilisé depuis PHP 5.5. UConverter est un meilleur choix si vous utilisez l’extension intl et n’utilisez pas mbstring.

function replace_invalid_byte_sequence($str)
{
    return UConverter::transcode($str, 'UTF-8', 'UTF-8');
}

function replace_invalid_byte_sequence2($str)
{
    return (new UConverter('UTF-8', 'UTF-8'))->convert($str);
}

htmlspecialchars peut être utilisé pour supprimer une séquence d'octets non valide depuis PHP 5.4. Htmlspecialchars est préférable à preg_match pour la gestion d'une grande taille d'octet et de sa précision. Une bonne partie de la mauvaise implémentation en utilisant une expression régulière est visible. .

function replace_invalid_byte_sequence3($str)
{
    return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8'));
}
6
masakielastic
$string = preg_replace('~&([a-z]{1,2})(acute|cedil|circ|Grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', htmlentities($string, ENT_COMPAT, 'UTF-8'));
3
Alix Axel

Du correctif récent au module d'analyse JSON de Drupal Feeds:

//remove everything except valid letters (from any language)
$raw = preg_replace('/(?:\\\\u[\pL\p{Zs}])+/', '', $raw);

Si vous êtes concerné oui, il conserve les espaces en tant que caractères valides.

Fait ce dont j'avais besoin. Il supprime les caractères emoji répandus de nos jours qui ne rentrent pas dans le jeu de caractères 'utf8' de MySQL et qui m'ont donné des erreurs du type "SQLSTATE [HY000]: Erreur générale: 1366 Valeur de chaîne incorrecte".

Pour plus de détails, voir https://www.drupal.org/node/1824506#comment-6881382

2
Oleksii Chekulaiev

Les règles sont donc les suivantes: le premier octet TF-8 a le bit haut défini en tant que marqueur, puis 1 à 4 bits pour indiquer le nombre d’octlets supplémentaires; ensuite, les deux bits hauts doivent être réglés sur 10 pour chacun des octlets supplémentaires.

Le pseudo-python serait:

newstring = ''
cont = 0
for each ch in string:
  if cont:
    if (ch >> 6) != 2: # high 2 bits are 10
      # do whatever, e.g. skip it, or skip whole point, or?
    else:
      # acceptable continuation of multi-octlet char
      newstring += ch
    cont -= 1
  else:
    if (ch >> 7): # high bit set?
      c = (ch << 1) # strip the high bit marker
      while (c & 1): # while the high bit indicates another octlet
        c <<= 1
        cont += 1
        if cont > 4:
           # more than 4 octels not allowed; cope with error
      if !cont:
        # illegal, do something sensible
      newstring += ch # or whatever
if cont:
  # last utf-8 was not terminated, cope

Cette même logique devrait être traduisible en php. Cependant, il n'est pas clair quel type de strip-tease doit être fait une fois que vous avez un personnage mal formé.

1
Will

Pour supprimer tous les caractères Unicode en dehors du plan de langage de base Unicode:

$str = preg_replace("/[^\\x00-\\xFFFF]/", "", $str);
1
Daniel Powers

Un peu différent de la question, mais ce que je fais est d'utiliser HtmlEncode (chaîne),

pseudo code ici

var encoded = HtmlEncode(string);
encoded = Regex.Replace(encoded, "&#\d+?;", "");
var result = HtmlDecode(encoded);

entrée et sortie

"Headlight\x007E Bracket, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?"
"Headlight~ Bracket, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?"

Je sais que ce n'est pas parfait, mais fait le travail pour moi.

0
misaxi