Entfernen Sie Nicht-utf8-Zeichen aus der Zeichenfolge

112

Ich habe ein Problem mit dem Entfernen von Nicht-Utf8-Zeichen aus der Zeichenfolge, die nicht richtig angezeigt werden. Zeichen sind wie folgt 0x97 0x61 0x6C 0x6F (hexadezimale Darstellung)

Was ist der beste Weg, um sie zu entfernen? Regulärer Ausdruck oder etwas anderes?

Dan Sosedoff
quelle
1
Die hier aufgeführten Lösungen haben bei mir nicht funktioniert, daher habe ich meine Antwort hier im Abschnitt " Zeichenüberprüfung
bobef
Im Zusammenhang damit , aber nicht unbedingt ein Duplikat, eher wie ein enger Cousin :)
Wayne Weibel

Antworten:

87

Verwenden eines Regex-Ansatzes:

$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);

Es sucht nach UTF-8-Sequenzen und erfasst diese in Gruppe 1. Es stimmt auch mit einzelnen Bytes überein, die nicht als Teil einer UTF-8-Sequenz identifiziert werden konnten, diese jedoch nicht erfassen. Ersetzen ist alles, was in Gruppe 1 erfasst wurde. Dadurch werden effektiv alle ungültigen Bytes entfernt.

Es ist möglich, die Zeichenfolge zu reparieren, indem die ungültigen Bytes als UTF-8-Zeichen codiert werden. Wenn die Fehler jedoch zufällig sind, können einige seltsame Symbole zurückbleiben.

$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);

BEARBEITEN:

  • !empty(x) stimmt mit nicht leeren Werten überein ("0" wird als leer betrachtet).
  • x != ""stimmt mit nicht leeren Werten überein, einschließlich "0".
  • x !== ""passt zu allem außer "".

x != "" scheint in diesem Fall die beste zu sein.

Ich habe das Match auch ein wenig beschleunigt. Anstatt jedes Zeichen einzeln abzugleichen, werden Sequenzen gültiger UTF-8-Zeichen abgeglichen.

Markus Jarderot
quelle
Was soll stattdessen $regex = <<<'END'für PHP <5.3.x verwendet werden?
Serhio
Sie können sie stattdessen in das Heredoc-Format konvertieren, wobei die Lesbarkeit leicht beeinträchtigt wird. Eine andere Möglichkeit besteht darin, Zeichenfolgen in einfachen Anführungszeichen zu verwenden, aber dann müssen Sie die Kommentare entfernen.
Markus Jarderot
In dieser Zeile befindet sich ein kleiner Tippfehler, den elseif (!empty($captures([2])) {Sie !== ""anstelle von leer verwenden sollten, da er "0"als leer betrachtet wird. Auch diese Funktion ist sehr langsam, könnte dies schneller erfolgen?
Kendall Hopkins
2
Dieser Ausdruck hat ein großes Speicherproblem, siehe hier .
Ja͢ck
1
@ MarkusJarderot, Regex ....... hmm, ist diese Funktion produktionsbereit? Gibt es Testfälle für diese Funktion?
Pacerier
132

Wenn Sie utf8_encode()eine bereits UTF8-Zeichenfolge anwenden , wird eine verstümmelte UTF8-Ausgabe zurückgegeben.

Ich habe eine Funktion erstellt, die all diese Probleme behebt. Es heißt Encoding::toUTF8().

Sie müssen nicht wissen, wie Ihre Zeichenfolgen codiert sind. Es kann Latin1 (ISO8859-1), Windows-1252 oder UTF8 sein, oder die Zeichenfolge kann eine Mischung aus diesen haben. Encoding::toUTF8()konvertiert alles in UTF8.

Ich habe es getan, weil mir ein Dienst einen Datenfeed gegeben hat, der alles durcheinander gebracht hat und diese Codierungen in derselben Zeichenfolge gemischt hat.

Verwendung:

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

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

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

Ich habe eine weitere Funktion hinzugefügt, Encoding :: fixUTF8 (), die jede UTF8-Zeichenfolge korrigiert, die nach mehrmaliger Codierung in UTF8 verstümmelt aussieht.

Verwendung:

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

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

Beispiele:

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");

wird ausgegeben:

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

Herunterladen:

https://github.com/neitanod/forceutf8

Sebastián Grignoli
quelle
13
Hervorragendes Zeug! Alle anderen Lösungen verwerfen ungültige Zeichen, aber diese behebt das Problem. Genial.
Giorgio79
4
Du hast die tolle Funktion gemacht! Ich habe in der Vergangenheit viel mit XML-Feeds gearbeitet und hatte immer ein Problem mit der Codierung. Danke dir.
Kostanos
5
ICH LIEBE DICH. Sie haben mir STUNDEN "Bloomoin" -Arbeit an schlechten UTF8-Zeichen erspart. Vielen Dank.
John Ballinger
4
Das ist fantastisch. Vielen Dank
EdgeCaseBerg
2
wunderbar, gut gemacht! Ich bin froh, dass ich das gefunden habe. Ich wünschte, ich könnte mit +100 abstimmen
;-)
61

Sie können mbstring verwenden:

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

... entfernt ungültige Zeichen.

Siehe: Durch Ersetzen ungültiger UTF-8-Zeichen durch Fragezeichen wird mbstring.substitute_character ignoriert

Frosty Z.
quelle
1
@ Alliswell welche? Könnten Sie bitte ein Beispiel nennen?
Frosty Z
sicher,<0x1a>
Alliswell
1
@Alliswell Wenn ich mich nicht irre <0x1a>, obwohl kein druckbares Zeichen, ist eine perfekt gültige UTF-8-Sequenz. Möglicherweise haben Sie Probleme mit nicht druckbaren Zeichen? Überprüfen Sie dies: stackoverflow.com/questions/1176904/…
Frosty Z
Ja, das ist der Fall. Danke, Kumpel!
Alliswell
Bevor ich mb convert aufrief, musste ich das Ersatzzeichen mbstring auf none setzen, ini_set('mbstring.substitute_character', 'none');sonst bekam ich Fragezeichen im Ergebnis.
cby016
21

Diese Funktion entfernt alle NICHT-ASCII-Zeichen. Sie ist nützlich, löst aber nicht die Frage:
Dies ist meine Funktion, die unabhängig von der Codierung immer funktioniert:

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;
}

Wie es funktioniert:

echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you?
David D.
quelle
8
Warum All-Caps-Funktionsnamen? Ewww.
Chris Baker
5
es ist ASCII und nicht einmal nah an dem, was die Frage wollte.
Misaxi
1
Dieser hat funktioniert. Ich hatte das Problem, als die Google Maps-API den Fehler aufgrund eines Nicht-UTF-8-Zeichens in der API-Anforderungs-URL meldete. Der Schuldige war ein íZeichen im Adressfeld, das ein gültiges UTF-8-Zeichen ist ( siehe Tabelle) . Die Moral: API-Fehlermeldungen nicht vertrauen :)
Valentine Shi
17
$text = iconv("UTF-8", "UTF-8//IGNORE", $text);

Das benutze ich. Scheint ziemlich gut zu funktionieren. Entnommen aus http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/

Znarkus
quelle
hat bei mir nicht funktioniert. Ich wünschte, ich könnte die getestete Leitung anhängen, aber leider hat sie ungültige Zeichen.
Nir O.
3
Entschuldigung, nach einigen weiteren Tests wurde mir klar, dass dies nicht wirklich das tat, was ich dachte. Ich benutze jetzt stackoverflow.com/a/8215387/138023
Znarkus
14

Versuche dies:

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

Laut iconv-Handbuch die Funktion den ersten Parameter als Eingabezeichensatz, den zweiten Parameter als Ausgabezeichensatz und den dritten als tatsächliche Eingabezeichenfolge.

Wenn Sie sowohl den Eingabe- als auch den Ausgabezeichensatz auf UTF-8 setzen und das //IGNOREFlag an den Ausgabezeichensatz anhängen , werden alle Zeichen in der Eingabezeichenfolge gelöscht, die nicht durch den Ausgabezeichensatz dargestellt werden können. Somit wird die Eingabezeichenfolge effektiv gefiltert.

Technoarya
quelle
Erklären Sie, was Ihre Antwort bewirkt, anstatt ein Code-Snippet zu sichern.
Tomasz Kowalczyk
3
Ich habe dies versucht, und das //IGNOREscheint den Hinweis nicht zu unterdrücken, dass ungültiges UTF-8 vorhanden ist (was ich natürlich weiß und beheben möchte). Ein hoch bewerteter Kommentar im Handbuch scheint zu glauben, dass es sich seit einigen Jahren um einen Fehler handelt.
Halfer
Ist immer besser zu bedienen iconv. @halfer Möglicherweise stammen Ihre Eingabedaten nicht von utf-8. Eine andere Möglichkeit besteht darin, eine erneute Konvertierung in ASCII und dann wieder zurück in utf-8 durchzuführen. In meinem Fall habe ich iconvwie$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
m3nda
@ erm3nda: Ich erinnere mich genau nicht an meinen Anwendungsfall dafür - möglicherweise wurde eine UTF-8-Website analysiert, die mit dem falschen Zeichensatz deklariert wurde. Vielen Dank für den Hinweis, ich bin sicher, dass dies für einen zukünftigen Leser nützlich sein wird.
Halfer
Ja, wenn Sie etwas nicht wissen, testen Sie es einfach und schließlich drücken Sie die Taste ;-)
m3nda
6

UConverter kann seit PHP 5.5 verwendet werden. UConverter ist die bessere Wahl, wenn Sie die Erweiterung intl und nicht mbstring verwenden.

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 können verwendet werden, um ungültige Bytesequenzen seit PHP 5.4 zu entfernen. Htmlspecialchars ist besser als preg_match, um große Bytes und die Genauigkeit zu verarbeiten. Viele der falschen Implementierungen durch die Verwendung von regulären Ausdrücken sind erkennbar.

function replace_invalid_byte_sequence3($str)
{
    return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8'));
}
masakielastisch
quelle
Sie haben drei nette Lösungen, aber es ist nicht klar, wie ein Benutzer zwischen ihnen wählen würde.
Bob Ray
6

Ich habe eine Funktion erstellt, die ungültige UTF-8-Zeichen aus einer Zeichenfolge löscht. Ich verwende es, um die Beschreibung von 27000 Produkten zu löschen, bevor die XML-Exportdatei generiert wird.

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;
}
Mumin
quelle
Von all den komplexen Antworten oben hat diese den Trick für mich getan! Vielen Dank.
Emin Özlem
Diese Funktion verwirrt mich. ord()Gibt Ergebnisse im Bereich von 0 bis 255 zurück. Der Riese ifin dieser Funktion testet auf Unicode-Bereiche, ord()die niemals zurückkehren werden. Wenn jemand klären möchte, warum diese Funktion so funktioniert, würde ich die Einsicht schätzen.
i336_
4

Willkommen zu 2019 und dem /uModifikator in Regex, der UTF-8-Multibyte-Zeichen für Sie verarbeitet

Wenn Sie nur verwenden mb_convert_encoding($value, 'UTF-8', 'UTF-8'), werden immer noch nicht druckbare Zeichen in Ihrer Zeichenfolge angezeigt

Diese Methode wird:

  • Entfernen Sie alle ungültigen UTF-8-Multibyte-Zeichen mit mb_convert_encoding
  • Entfernen Sie alle nicht druckbare Zeichen wie \r, \x00(NULL-Byte) und andere Steuer Zeichen mitpreg_replace

Methode:

function utf8_filter(string $value): string{
    return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}

[:print:]\nPassen Sie alle druckbaren Zeichen und Zeilenumbrüche an und entfernen Sie alles andere

Sie können die ASCII-Tabelle unten sehen. Die druckbaren Zeichen reichen von 32 bis 127, aber Zeilenumbruch \nist Teil der Steuerzeichen, die von 0 bis 31 reichen, sodass wir dem regulären Ausdruck Zeilenumbruch hinzufügen müssen/[^[:print:]\n]/u

https://cdn.shopify.com/s/files/1/1014/5789/files/Standard-ASCII-Table_large.jpg?10669400161723642407

Sie können versuchen, Zeichenfolgen mit Zeichen außerhalb des druckbaren Bereichs wie \x7F(DEL), \x1B(Esc) usw. durch die Regex zu senden und zu sehen, wie sie entfernt werden

function utf8_filter(string $value): string{
    return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}

$arr = [
    'Danish chars'          => 'Hello from Denmark with æøå',
    'Non-printable chars'   => "\x7FHello with invalid chars\r \x00"
];

foreach($arr as $k => $v){
    echo "$k:\n---------\n";
    
    $len = strlen($v);
    echo "$v\n(".$len.")\n";
    
    $strip = utf8_decode(utf8_filter(utf8_encode($v)));
    $strip_len = strlen($strip);
    echo $strip."\n(".$strip_len.")\n\n";
    
    echo "Chars removed: ".($len - $strip_len)."\n\n\n";
}

https://www.tehplayground.com/q5sJ3FOddhv1atpR

Clarkk
quelle
Willkommen im Jahr 2047, wo php-mbstringstandardmäßig nicht in PHP gepackt ist.
NVRM
3
$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'));
Alix Axel
quelle
2

Vom letzten Patch zum JSON-Parser-Modul Feeds von Drupal:

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

Wenn Sie besorgt sind, werden Leerzeichen als gültige Zeichen beibehalten.

Habe was ich brauchte. Es entfernt heutzutage weit verbreitete Emoji-Zeichen, die nicht in den 'utf8'-Zeichensatz von MySQL passen und die mir Fehler wie "SQLSTATE [HY000]: Allgemeiner Fehler: 1366 Falscher Zeichenfolgenwert" gaben.

Weitere Informationen finden Sie unter https://www.drupal.org/node/1824506#comment-6881382

Oleksii Chekulaiev
quelle
Das iconvist weitaus besser als das altmodische Regexp-basierte preg_replace, das heutzutage veraltet ist.
m3nda
3
preg_replace ist nicht veraltet
Oleksii Chekulaiev
1
Sie haben vollkommen recht ereg_replace(), tut mir leid.
m3nda
2

Vielleicht nicht die genaueste Lösung, aber sie erledigt die Arbeit mit einer einzigen Codezeile:

echo str_replace("?","",(utf8_decode($str)));

utf8_decodekonvertiert die Zeichen in ein Fragezeichen;
str_replacewird die Fragezeichen entfernen.

user12602477
quelle
Nachdem Sie Hunderte von Lösungen ausprobiert haben, ist die einzige Lösung, die funktioniert hat, Ihre.
Haritsinh Gohil
1

Die Regeln lauten also, dass für das erste UTF-8- Oktlet das High-Bit als Marker gesetzt ist und dann 1 bis 4 Bits, um anzugeben, wie viele zusätzliche Octlets vorhanden sind. dann müssen für jedes der zusätzlichen Oktlets die hohen zwei Bits auf 10 gesetzt sein.

Die Pseudo-Python wäre:

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

Dieselbe Logik sollte in PHP übersetzbar sein. Es ist jedoch nicht klar, welche Art von Strippen durchgeführt werden soll, wenn Sie einen missgebildeten Charakter erhalten.

Wille
quelle
c = (ch << 1)wird (c & 1)beim ersten Mal Null machen und die Schleife überspringen. Der Test sollte wahrscheinlich sein(c & 128)
Markus Jarderot
1

So entfernen Sie alle Unicode-Zeichen außerhalb der Unicode-Grundspracheebene:

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

Etwas anders als die Frage, aber ich verwende HtmlEncode (Zeichenfolge).

Pseudocode hier

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

Eingabe und Ausgabe

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

Ich weiß, dass es nicht perfekt ist, aber es macht den Job für mich.

Misaxi
quelle
0
static $preg = <<<'END'
%(
[\x09\x0A\x0D\x20-\x7E]
| [\xC2-\xDF][\x80-\xBF]
| \xE0[\xA0-\xBF][\x80-\xBF]
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
| \xED[\x80-\x9F][\x80-\xBF]
| \xF0[\x90-\xBF][\x80-\xBF]{2}
| [\xF1-\xF3][\x80-\xBF]{3}
| \xF4[\x80-\x8F][\x80-\xBF]{2}
)%xs
END;
if (preg_match_all($preg, $string, $match)) {
    $string = implode('', $match[0]);
} else {
    $string = '';
}

es funktioniert auf unseren Service

llluo
quelle
2
Können Sie einen Kontext hinzufügen, um zu erklären, wie die Frage beantwortet wird, anstatt nur Code zu beantworten?
Arun Vinoth
-1

Wie wäre es mit iconv:

http://php.net/manual/en/function.iconv.php

Ich habe es nicht in PHP selbst verwendet, aber es hat in der Kommandozeile immer gute Ergebnisse für mich erzielt. Sie können damit ungültige Zeichen ersetzen.

Ben
quelle