Mam problem z usunięciem znaków innych niż utf8 z łańcucha, które nie wyświetlają się poprawnie. Znaki są takie jak ten 0x97 0x61 0x6C 0x6F (reprezentacja szesnastkowa)
Jaki jest najlepszy sposób ich usunięcia? Wyrażenie regularne czy coś innego?
Mam problem z usunięciem znaków innych niż utf8 z łańcucha, które nie wyświetlają się poprawnie. Znaki są takie jak ten 0x97 0x61 0x6C 0x6F (reprezentacja szesnastkowa)
Jaki jest najlepszy sposób ich usunięcia? Wyrażenie regularne czy coś innego?
Odpowiedzi:
Korzystanie z podejścia 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);
Wyszukuje sekwencje UTF-8 i przechwytuje je w grupie 1. Pasuje również do pojedynczych bajtów, których nie można zidentyfikować jako części sekwencji UTF-8, ale ich nie przechwytuje. Zastąpieniem jest wszystko, co zostało przechwycone w grupie 1. To skutecznie usuwa wszystkie nieprawidłowe bajty.
Możliwa jest naprawa ciągu poprzez zakodowanie nieprawidłowych bajtów jako znaków UTF-8. Ale jeśli błędy są przypadkowe, może to pozostawić dziwne symbole.
$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);
EDYTOWAĆ:
!empty(x)
dopasuje niepuste wartości ( "0"
jest uważane za puste).x != ""
dopasuje niepuste wartości, w tym "0"
.x !== ""
dopasuje wszystko oprócz ""
.x != ""
wydaje się najlepszy do użycia w tym przypadku.
Trochę przyśpieszyłem też mecz. Zamiast dopasowywać każdy znak osobno, dopasowuje sekwencje prawidłowych znaków UTF-8.
$regex = <<<'END'
PHP <5.3.x?
elseif (!empty($captures([2])) {
i powinieneś użyć !== ""
zamiast pustego, ponieważ "0"
jest uważany za pusty. Ta funkcja jest również bardzo powolna, czy można to zrobić szybciej?
Jeśli zastosujesz się utf8_encode()
do już napisu UTF8, zwróci to zniekształcone wyjście UTF8.
Stworzyłem funkcję, która rozwiązuje wszystkie te problemy. To się nazywa Encoding::toUTF8()
.
Nie musisz wiedzieć, jakie jest kodowanie twoich ciągów. Może to być Latin1 (ISO8859-1), Windows-1252 lub UTF8 albo ciąg znaków może mieć ich mieszankę. Encoding::toUTF8()
przekonwertuje wszystko do UTF8.
Zrobiłem to, ponieważ usługa dostarczała mi wszystkie pomieszane dane, mieszając te kodowania w tym samym ciągu.
Stosowanie:
require_once('Encoding.php');
use \ForceUTF8\Encoding; // It's namespaced now.
$utf8_string = Encoding::toUTF8($mixed_string);
$latin1_string = Encoding::toLatin1($mixed_string);
Dołączyłem inną funkcję, Encoding :: fixUTF8 (), która naprawia każdy ciąg znaków UTF8, który wygląda na zniekształcony produkt wielokrotnego zakodowania w UTF8.
Stosowanie:
require_once('Encoding.php');
use \ForceUTF8\Encoding; // It's namespaced now.
$utf8_string = Encoding::fixUTF8($garbled_utf8_string);
Przykłady:
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");
wyświetli:
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Pobieranie:
Możesz użyć mbstring:
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
... usunie nieprawidłowe znaki.
<0x1a>
<0x1a>
, chociaż nie jest to znak drukowalny, jest to całkowicie poprawna sekwencja UTF-8. Możesz mieć problemy ze znakami niedrukowalnymi? Sprawdź to: stackoverflow.com/questions/1176904/…
ini_set('mbstring.substitute_character', 'none');
przeciwnym razie otrzymywałem znaki zapytania w wyniku.
Ta funkcja usuwa wszystkie znaki inne niż ASCII, jest przydatna, ale nie rozwiązuje pytania:
To moja funkcja, która zawsze działa, niezależnie od kodowania:
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;
}
Jak to działa:
echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you?
í
znak w polu adresu, który JEST prawidłowym znakiem UTF-8, patrz tabela . Morale: nie ufaj komunikatom o błędach API :)
$text = iconv("UTF-8", "UTF-8//IGNORE", $text);
To jest to, czego używam. Wydaje się, że działa całkiem nieźle. Zaczerpnięte z http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/
Spróbuj tego:
$string = iconv("UTF-8","UTF-8//IGNORE",$string);
Zgodnie z instrukcją iconv , funkcja przyjmie pierwszy parametr jako zestaw znaków wejściowych, drugi parametr jako zestaw znaków wyjściowych, a trzeci jako rzeczywisty łańcuch wejściowy.
Jeśli ustawisz zarówno wejściowy, jak i wyjściowy zestaw znaków na UTF-8 i dodasz //IGNORE
flagę do wyjściowego zestawu znaków, funkcja usunie (usunie) wszystkie znaki w ciągu wejściowym, których nie może reprezentować wyjściowy zestaw znaków. W ten sposób działa filtrowanie ciągu wejściowego.
//IGNORE
nie wydaje się, aby pomijał informację, że obecny jest nieprawidłowy UTF-8 (co oczywiście wiem i chcę naprawić). Wysoko oceniany komentarz w podręczniku wydaje się sugerować, że był to błąd od kilku lat.
iconv
. @halfer Może twoje dane wejściowe nie pochodzą z utf-8. Inną opcją jest dokonanie ponownej konwersji na ascii, a następnie z powrotem na utf-8. W moim przypadku użyłem iconv
tak$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
Tekst może zawierać znak inny niż utf8 . Spróbuj najpierw:
$nonutf8 = mb_convert_encoding($nonutf8 , 'UTF-8', 'UTF-8');
Możesz przeczytać więcej na ten temat tutaj: http://php.net/manual/en/function.mb-convert-encoding.php news
UConverter może być używany od PHP 5.5. UConverter jest lepszym wyborem, jeśli używasz rozszerzenia intl i nie używasz 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 może służyć do usuwania nieprawidłowej sekwencji bajtów od PHP 5.4. Htmlspecialchars jest lepszy niż preg_match do obsługi dużych rozmiarów bajtów i dokładności. Widać wiele nieprawidłowych implementacji przy użyciu wyrażeń regularnych.
function replace_invalid_byte_sequence3($str)
{
return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8'));
}
Zrobiłem funkcję, która usuwa nieprawidłowe znaki UTF-8 z ciągu. Używam go do jasnego opisu 27000 produktów, zanim wygeneruje plik eksportu 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;
}
ord()
zwraca wyniki z zakresu 0-255. Gigant if
w tej funkcji testuje zakresy Unicode, ord()
które nigdy nie wrócą. Jeśli ktoś chce wyjaśnić, dlaczego ta funkcja działa tak, jak działa, byłbym wdzięczny za wgląd.
Witamy w roku 2019 i /u
modyfikatorze w wyrażeniu regularnym, który będzie obsługiwał za Ciebie wielobajtowe znaki UTF-8
Jeśli użyjesz tylko mb_convert_encoding($value, 'UTF-8', 'UTF-8')
znaków, w swoim ciągu nadal będziesz mieć niedrukowalne znaki
Ta metoda:
mb_convert_encoding
\r
, \x00
(null-bajt) i inne znaki kontrolne zpreg_replace
function utf8_filter(string $value): string{
return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}
[:print:]
dopasuj wszystkie drukowalne znaki i znaki \n
nowej linii oraz usuń wszystko inne
Możesz zobaczyć tabelę ASCII poniżej .. Znaki drukowalne mieszczą się w zakresie od 32 do 127, ale \n
znak nowej linii jest częścią znaków kontrolnych z zakresu od 0 do 31, więc musimy dodać nową linię do wyrażenia regularnego/[^[:print:]\n]/u
Możesz spróbować wysłać ciągi przez wyrażenie regularne ze znakami spoza drukowalnego zakresu, jak \x7F
(DEL), \x1B
(Esc) itp. I zobaczyć, jak są one usuwane
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";
}
php-mbstring
domyślnie nie jest spakowany w php.
Od ostatniej łatki do modułu parsera JSON Feeds w Drupalu:
//remove everything except valid letters (from any language)
$raw = preg_replace('/(?:\\\\u[\pL\p{Zs}])+/', '', $raw);
Jeśli martwisz się, tak, zachowuje spacje jako prawidłowe znaki.
Zrobiłem to, czego potrzebowałem. Usuwa szeroko rozpowszechnione obecnie znaki emoji, które nie pasują do zestawu znaków MySQL „utf8”, co dało mi błędy typu „SQLSTATE [HY000]: Błąd ogólny: 1366 Niepoprawna wartość ciągu”.
Aby uzyskać szczegółowe informacje, zobacz https://www.drupal.org/node/1824506#comment-6881382
iconv
on znacznie lepszy niż staroświecki regexp preg_replace
, który obecnie jest przestarzały.
ereg_replace()
, przepraszam.
Może nie jest to najbardziej precyzyjne rozwiązanie, ale wykonuje zadanie za pomocą jednej linii kodu:
echo str_replace("?","",(utf8_decode($str)));
utf8_decode
zamieni znaki na znak zapytania;
str_replace
usunie znaki zapytania.
Reguły są więc takie, że pierwszy oktlet UTF-8 ma ustawiony wysoki bit jako znacznik, a następnie od 1 do 4 bitów, aby wskazać, ile dodatkowych oktletów; wtedy każdy z dodatkowych oktletów musi mieć dwa wysokie bity ustawione na 10.
Pseudo-Python wyglądałby tak:
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
Ta sama logika powinna być przetłumaczalna na php. Jednak nie jest jasne, jaki rodzaj strippingu należy wykonać, gdy pojawi się zdeformowana postać.
c = (ch << 1)
zrobi (c & 1)
zero za pierwszym razem, pomijając pętlę. Test prawdopodobnie powinien być(c & 128)
Aby usunąć wszystkie znaki Unicode spoza podstawowej płaszczyzny języka Unicode:
$str = preg_replace("/[^\\x00-\\xFFFF]/", "", $str);
Trochę inaczej niż w pytaniu, ale ja robię to używając HtmlEncode (string),
pseudo kod tutaj
var encoded = HtmlEncode(string);
encoded = Regex.Replace(encoded, "&#\d+?;", "");
var result = HtmlDecode(encoded);
wejście i wyjście
"Headlight\x007E Bracket, { Cafe Racer<> Style, Stainless Steel 中文呢?"
"Headlight~ Bracket, { Cafe Racer<> Style, Stainless Steel 中文呢?"
Wiem, że to nie jest idealne, ale spełnia swoje zadanie.
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 = '';
}
to działa w naszej usłudze
A co z iconv:
http://php.net/manual/en/function.iconv.php
Nie używałem go w samym PHP, ale zawsze działał dobrze w wierszu poleceń. Możesz go zmusić do zastępowania nieprawidłowych znaków.