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?
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.
Zamiast tego można przekonwertować je do formatu heredoc, z niewielką utratą czytelności. Inną możliwością jest użycie pojedynczych cudzysłowów, ale wtedy będziesz musiał usunąć komentarze.
Markus Jarderot
W tym wierszu jest mała literówka 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?
Kendall Hopkins
2
To wyrażenie ma poważny problem z pamięcią, patrz tutaj .
Ja͢ck
1
@MarkusJarderot, Regex ....... hmm, czy ta funkcja jest gotowa do produkcji? Czy istnieją przypadki testowe dla tej funkcji?
Pacerier
132
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.
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.
@Alliswell, które z nich? Czy mógłbyś podać przykład?
Frosty Z
<0x1a>
jasne
1
@Alliswell Jeśli się nie mylę <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/…
Frosty Z
tak, o to chodzi. Dzięki stary!
Alliswell,
Przed wywołaniem mb convert, musiałem ustawić znak zastępczy mbstring na none, w ini_set('mbstring.substitute_character', 'none');przeciwnym razie otrzymywałem znaki zapytania w wyniku.
cby016
21
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:
Dlaczego nazwy funkcji pisane wielkimi literami? Ewww.
Chris Baker,
5
jest to ASCII i nawet nie jest zbliżone do tego, o co chodziło w pytaniu.
misaxi
1
Ten działał. Napotkałem problem, gdy interfejs API Map Google zgłosił błąd z powodu „znaku innego niż UTF-8” w adresie URL żądania API. Winowajcą był íznak w polu adresu, który JEST prawidłowym znakiem UTF-8, patrz tabela . Morale: nie ufaj komunikatom o błędach API :)
nie działa dla mnie. Chciałbym móc dołączyć testowaną linię, ale niestety ma ona nieprawidłowe znaki.
Nir O.
3
Przepraszam, po kilku dalszych testach zdałem sobie sprawę, że to nie działa tak, jak myślałem. Teraz używam stackoverflow.com/a/8215387/138023
Znarkus
14
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 //IGNOREflagę 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.
Wyjaśnij, co robi twoja odpowiedź, zamiast porzucać fragment kodu.
Tomasz Kowalczyk
3
Wypróbowałem to i //IGNOREnie 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.
halfer
Zawsze lepiej jest używać 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 iconvtak$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
m3nda
@ erm3nda: Dokładnie nie pamiętam swojego przypadku użycia w tym przypadku - mogłem przeanalizować witrynę internetową UTF-8 zadeklarowaną z niewłaściwym zestawem znaków. Dzięki za notatkę, jestem pewien, że przyda się przyszłemu czytelnikowi.
halfer
Tak, jeśli czegoś nie wiesz, po prostu przetestuj, a na koniec uderzysz w klawisz ;-)
m3nda
9
Tekst może zawierać znak inny niż utf8 . Spróbuj najpierw:
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){returnUConverter::transcode($str,'UTF-8','UTF-8');}function replace_invalid_byte_sequence2($str){return(newUConverter('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'));}
Ze wszystkich złożonych odpowiedzi powyżej, ta załatwiła mi sprawę! Dzięki.
Emin Özlem
Jestem zdezorientowany tą funkcją. ord()zwraca wyniki z zakresu 0-255. Gigant ifw 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.
i336_
4
Witamy w roku 2019 i /umodyfikatorze 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:
Usuń wszystkie nieprawidłowe wielobajtowe znaki UTF-8 za pomocą mb_convert_encoding
Usuń wszystkie niedrukowalne znaki takie jak \r, \x00(null-bajt) i inne znaki kontrolne zpreg_replace
metoda:
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 \nnowej linii oraz usuń wszystko inne
Możesz zobaczyć tabelę ASCII poniżej .. Znaki drukowalne mieszczą się w zakresie od 32 do 127, ale \nznak 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";}
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”.
Po wypróbowaniu setek rozwiązań jedyne, które działało, jest Twoje.
Haritsinh Gohil
1
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 =0for each ch instring: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 -=1else:if(ch >>7):# high bit set?
c =(ch <<1)# strip the high bit markerwhile(c &1):# while the high bit indicates another octlet
c <<=1
cont +=1if cont >4:# more than 4 octels not allowed; cope with errorif!cont:# illegal, do something sensible
newstring += ch # or whateverif 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ć.
Odpowiedzi:
Korzystanie z podejścia regex:
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.
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.
źródło
$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:
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:
Przykłady:
wyświetli:
Pobieranie:
https://github.com/neitanod/forceutf8
źródło
Możesz użyć mbstring:
... usunie nieprawidłowe znaki.
Zobacz: Zastępowanie nieprawidłowych znaków UTF-8 znakami zapytania, mbstring.substitute_character wydaje się ignorowane
źródło
<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:
Jak to działa:
źródło
í
znak w polu adresu, który JEST prawidłowym znakiem UTF-8, patrz tabela . Morale: nie ufaj komunikatom o błędach API :)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/
źródło
Spróbuj tego:
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.źródło
//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łemiconv
tak$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
Tekst może zawierać znak inny niż utf8 . Spróbuj najpierw:
Możesz przeczytać więcej na ten temat tutaj: http://php.net/manual/en/function.mb-convert-encoding.php news
źródło
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.
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.
źródło
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.
źródło
ord()
zwraca wyniki z zakresu 0-255. Gigantif
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-8Jeśli użyjesz tylko
mb_convert_encoding($value, 'UTF-8', 'UTF-8')
znaków, w swoim ciągu nadal będziesz mieć niedrukowalne znakiTa metoda:
mb_convert_encoding
\r
,\x00
(null-bajt) i inne znaki kontrolne zpreg_replace
metoda:
[:print:]
dopasuj wszystkie drukowalne znaki i znaki\n
nowej linii oraz usuń wszystko inneMoż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 usuwanehttps://www.tehplayground.com/q5sJ3FOddhv1atpR
źródło
php-mbstring
domyślnie nie jest spakowany w php.źródło
Od ostatniej łatki do modułu parsera JSON Feeds w Drupalu:
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
źródło
iconv
on znacznie lepszy niż staroświecki regexppreg_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:
utf8_decode
zamieni znaki na znak zapytania;str_replace
usunie znaki zapytania.źródło
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:
Ta sama logika powinna być przetłumaczalna na php. Jednak nie jest jasne, jaki rodzaj strippingu należy wykonać, gdy pojawi się zdeformowana postać.
źródło
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:
źródło
Trochę inaczej niż w pytaniu, ale ja robię to używając HtmlEncode (string),
pseudo kod tutaj
wejście i wyjście
Wiem, że to nie jest idealne, ale spełnia swoje zadanie.
źródło
to działa w naszej usłudze
źródło
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.
źródło