Tôi đang gặp sự cố với việc xóa các ký tự không phải utf8 khỏi chuỗi không hiển thị đúng cách. Các ký tự giống như thế này 0x97 0x61 0x6C 0x6F (biểu diễn hex)
Cách tốt nhất để loại bỏ chúng là gì? Biểu thức chính quy hay thứ gì khác?
Tôi đang gặp sự cố với việc xóa các ký tự không phải utf8 khỏi chuỗi không hiển thị đúng cách. Các ký tự giống như thế này 0x97 0x61 0x6C 0x6F (biểu diễn hex)
Cách tốt nhất để loại bỏ chúng là gì? Biểu thức chính quy hay thứ gì khác?
Câu trả lời:
Sử dụng phương pháp tiếp cận 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);
Nó tìm kiếm các trình tự UTF-8 và bắt chúng vào nhóm 1. Nó cũng khớp các byte đơn lẻ không thể được xác định là một phần của trình tự UTF-8, nhưng không nắm bắt các byte đó. Thay thế là bất cứ thứ gì được bắt vào nhóm 1. Điều này có hiệu quả loại bỏ tất cả các byte không hợp lệ.
Có thể sửa chữa chuỗi, bằng cách mã hóa các byte không hợp lệ dưới dạng ký tự UTF-8. Nhưng nếu lỗi là ngẫu nhiên, điều này có thể để lại một số ký hiệu lạ.
$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);
BIÊN TẬP:
!empty(x)
sẽ khớp với các giá trị không trống ( "0"
được coi là trống).x != ""
sẽ khớp với các giá trị không trống, bao gồm "0"
.x !== ""
sẽ khớp với bất cứ điều gì ngoại trừ ""
.x != ""
có vẻ là một trong những tốt nhất để sử dụng trong trường hợp này.
Tôi cũng đã tăng tốc trận đấu một chút. Thay vì khớp từng ký tự riêng biệt, nó khớp với các chuỗi ký tự UTF-8 hợp lệ.
$regex = <<<'END'
cho PHP <5.3.x?
elseif (!empty($captures([2])) {
và bạn nên sử dụng !== ""
thay vì trống vì "0"
được coi là trống. Ngoài ra chức năng này rất chậm, điều này có thể được thực hiện nhanh hơn không?
Nếu bạn áp dụng utf8_encode()
cho một chuỗi UTF8 đã có, nó sẽ trả về đầu ra UTF8 bị cắt xén.
Tôi đã tạo một hàm giải quyết tất cả các vấn đề này. Nó được gọi làEncoding::toUTF8()
.
Bạn không cần biết mã hóa các chuỗi của mình là gì. Nó có thể là Latin1 (ISO8859-1), Windows-1252 hoặc UTF8, hoặc chuỗi có thể có sự kết hợp của chúng.Encoding::toUTF8()
sẽ chuyển đổi mọi thứ sang UTF8.
Tôi đã làm điều đó vì một dịch vụ đang cung cấp cho tôi nguồn cấp dữ liệu, tất cả đều lộn xộn, trộn các mã hóa đó trong cùng một chuỗi.
Sử dụng:
require_once('Encoding.php');
use \ForceUTF8\Encoding; // It's namespaced now.
$utf8_string = Encoding::toUTF8($mixed_string);
$latin1_string = Encoding::toLatin1($mixed_string);
Tôi đã bao gồm một hàm khác, Encoding :: fixUTF8 (), sẽ sửa mọi chuỗi UTF8 trông giống như sản phẩm bị cắt xén do đã được mã hóa thành UTF8 nhiều lần.
Sử dụng:
require_once('Encoding.php');
use \ForceUTF8\Encoding; // It's namespaced now.
$utf8_string = Encoding::fixUTF8($garbled_utf8_string);
Ví dụ:
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");
sẽ xuất:
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ải xuống:
Bạn có thể sử dụng mbstring:
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
... sẽ loại bỏ các ký tự không hợp lệ.
<0x1a>
<0x1a>
, mặc dù không phải là ký tự in được, nhưng là một chuỗi UTF-8 hoàn toàn hợp lệ. Bạn có thể gặp sự cố với các ký tự không in được? Kiểm tra cái này: stackoverflow.com/questions/1176904/…
ini_set('mbstring.substitute_character', 'none');
nếu không tôi sẽ nhận được dấu chấm hỏi trong kết quả.
Hàm này loại bỏ tất cả các ký tự KHÔNG ASCII, nó hữu ích nhưng không giải quyết được câu hỏi:
Đây là hàm của tôi luôn hoạt động, bất kể mã hóa:
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;
}
Làm thế nào nó hoạt động:
echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you?
í
ký tự trong trường địa chỉ là ký tự UTF-8 hợp lệ, xem bảng . Tinh thần: không tin tưởng các thông báo lỗi API :)
$text = iconv("UTF-8", "UTF-8//IGNORE", $text);
Đây là những gì tôi đang sử dụng. Có vẻ hoạt động khá tốt. Lấy từ http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/
thử cái này:
$string = iconv("UTF-8","UTF-8//IGNORE",$string);
Theo hướng dẫn sử dụng iconv , hàm sẽ lấy tham số đầu tiên làm tập ký tự đầu vào, tham số thứ hai làm tập ký tự đầu ra và tham số thứ ba là chuỗi đầu vào thực tế.
Nếu bạn đặt cả bộ ký tự đầu vào và đầu ra thành UTF-8 và nối //IGNORE
cờ vào bộ ký tự đầu ra, hàm sẽ loại bỏ (dải) tất cả các ký tự trong chuỗi đầu vào mà bộ ký tự đầu ra không thể đại diện. Do đó, việc lọc chuỗi đầu vào có hiệu lực.
//IGNORE
dường như không ngăn chặn được thông báo rằng UTF-8 không hợp lệ đang hiện diện (tất nhiên, tôi biết và muốn sửa). Một nhận xét được đánh giá cao trong sách hướng dẫn dường như cho rằng nó đã bị lỗi trong một số năm.
iconv
. @halfer Có thể dữ liệu đầu vào của bạn không phải từ utf-8. Một tùy chọn khác là thực hiện chuyển đổi lại thành ascii sau đó quay lại utf-8 một lần nữa. Trong trường hợp của tôi, tôi đã sử dụng iconv
như$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
Văn bản có thể chứa ký tự không phải utf8 . Hãy thử làm trước:
$nonutf8 = mb_convert_encoding($nonutf8 , 'UTF-8', 'UTF-8');
Bạn có thể đọc thêm về nó tại đây: http://php.net/manual/en/ Chức năng.mb-convert-encoding.php news
UConverter có thể được sử dụng kể từ PHP 5.5. UConverter là lựa chọn tốt hơn nếu bạn sử dụng phần mở rộng intl và không sử dụng 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 có thể được sử dụng để loại bỏ chuỗi byte không hợp lệ kể từ PHP 5.4. Htmlspecialchars tốt hơn preg_match để xử lý kích thước byte lớn và độ chính xác. Có thể thấy rất nhiều việc triển khai sai khi sử dụng biểu thức chính quy.
function replace_invalid_byte_sequence3($str)
{
return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8'));
}
Tôi đã tạo một hàm xóa các ký tự UTF-8 không hợp lệ khỏi một chuỗi. Tôi đang sử dụng nó để xóa mô tả về 27000 sản phẩm trước khi nó tạo tệp xuất 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()
trả về kết quả trong phạm vi 0-255. Gã khổng lồ if
trong hàm này kiểm tra các dải unicode ord()
sẽ không bao giờ trả lại. Nếu ai đó muốn làm rõ lý do tại sao chức năng này hoạt động theo cách nó hoạt động, tôi đánh giá cao sự hiểu biết sâu sắc.
Chào mừng bạn đến với năm 2019 và công cụ /u
sửa đổi trong regex sẽ xử lý các ký tự đa byte UTF-8 cho bạn
Nếu bạn chỉ sử dụng, mb_convert_encoding($value, 'UTF-8', 'UTF-8')
bạn vẫn sẽ có các ký tự không in được trong chuỗi của bạn
Phương pháp này sẽ:
mb_convert_encoding
\r
, \x00
(NULL-byte) và các ký tự điều khiển khác bằngpreg_replace
function utf8_filter(string $value): string{
return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}
[:print:]
khớp với tất cả các ký tự có thể in và \n
dòng mới và loại bỏ mọi thứ khác
Bạn có thể xem bảng ASCII bên dưới .. Các ký tự có thể in được nằm trong khoảng từ 32 đến 127, nhưng dòng mới \n
là một phần của các ký tự điều khiển nằm trong khoảng từ 0 đến 31 vì vậy chúng tôi phải thêm dòng mới vào regex/[^[:print:]\n]/u
Bạn có thể thử gửi các chuỗi qua regex với các ký tự nằm ngoài phạm vi có thể in được như \x7F
(DEL), \x1B
(Esc), v.v. và xem cách chúng bị loại bỏ
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
không được đóng gói trong php theo mặc định.
Từ bản vá gần đây đến mô-đun phân tích cú pháp JSON Nguồn cấp dữ liệu của Drupal:
//remove everything except valid letters (from any language)
$raw = preg_replace('/(?:\\\\u[\pL\p{Zs}])+/', '', $raw);
Nếu bạn lo lắng, có, nó giữ lại khoảng trắng dưới dạng các ký tự hợp lệ.
Đã làm những gì tôi cần. Nó loại bỏ các ký tự biểu tượng cảm xúc phổ biến hiện nay không phù hợp với bộ ký tự 'utf8' của MySQL và điều đó gây ra cho tôi các lỗi như "SQLSTATE [HY000]: Lỗi chung: 1366 Giá trị chuỗi không chính xác".
Chi tiết xem tại https://www.drupal.org/node/1824506#comment-6881382
iconv
tốt hơn nhiều so với dựa trên regexp kiểu cũ preg_replace
, ngày nay không còn được dùng nữa.
ereg_replace()
, xin lỗi.
Có thể không phải là giải pháp chính xác nhất, nhưng nó hoàn thành công việc chỉ với một dòng mã:
echo str_replace("?","",(utf8_decode($str)));
utf8_decode
sẽ chuyển đổi các ký tự thành dấu chấm hỏi;
str_replace
sẽ loại bỏ các dấu chấm hỏi.
Vì vậy, các quy tắc là octlet UTF-8 đầu tiên có bit cao được đặt làm điểm đánh dấu, và sau đó là 1 đến 4 bit để cho biết có bao nhiêu octlet bổ sung; thì mỗi octlet bổ sung phải có hai bit cao được đặt thành 10.
Con trăn giả sẽ là:
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
Logic tương tự này sẽ được dịch sang php. Tuy nhiên, không rõ loại tước nào sẽ được thực hiện khi bạn nhận được một nhân vật bị dị dạng.
c = (ch << 1)
sẽ (c & 1)
bằng 0 lần đầu tiên, bỏ qua vòng lặp. Xét nghiệm này có lẽ nên được(c & 128)
Để xóa tất cả các ký tự Unicode bên ngoài bình diện ngôn ngữ cơ bản Unicode:
$str = preg_replace("/[^\\x00-\\xFFFF]/", "", $str);
Hơi khác với câu hỏi, nhưng những gì tôi đang làm là sử dụng HtmlEncode (chuỗi),
mã giả ở đây
var encoded = HtmlEncode(string);
encoded = Regex.Replace(encoded, "&#\d+?;", "");
var result = HtmlDecode(encoded);
đầu vào và đầu ra
"Headlight\x007E Bracket, { Cafe Racer<> Style, Stainless Steel 中文呢?"
"Headlight~ Bracket, { Cafe Racer<> Style, Stainless Steel 中文呢?"
Tôi biết nó không hoàn hảo, nhưng nó phù hợp với tôi.
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 = '';
}
nó hoạt động trên dịch vụ của chúng tôi
Làm thế nào về iconv:
http://php.net/manual/en/ Chức năng.iconv.php
Tôi chưa sử dụng nó bên trong PHP nhưng nó luôn hoạt động tốt đối với tôi trên dòng lệnh. Bạn có thể lấy nó để thay thế các ký tự không hợp lệ.