Xóa các ký tự không phải utf8 khỏi chuỗi


112

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?


1
Các giải pháp được liệt kê ở đây không hiệu quả với tôi vì vậy tôi đã tìm thấy câu trả lời của mình ở đây trong phần "Xác thực ký tự": webcollab.sourceforge.net/unicode.html
bobef

Về vấn đề này , nhưng không nhất thiết phải là một bản sao, giống như một cô em họ gần :)
Wayne Weibel

Câu trả lời:


87

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ệ.


sử dụng gì để thay thế $regex = <<<'END'cho PHP <5.3.x?
serhio

Thay vào đó, bạn có thể chuyển đổi chúng sang định dạng heredoc, với một hình phạt nhẹ để có thể đọc được. Một khả năng khác là sử dụng các chuỗi trích dẫn đơn, nhưng sau đó bạn sẽ phải xóa các nhận xét.
Markus Jarderot

Có một lỗi đánh máy nhỏ trong dòng này 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?
Kendall Hopkins

2
Biểu thức này có vấn đề lớn về bộ nhớ, xem tại đây .
Ja͢ck

1
@MarkusJarderot, Regex ....... hmm, chức năng này đã sẵn sàng chưa? Có trường hợp thử nghiệm cho chức năng này không?
Pacerier

132

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:

https://github.com/neitanod/forceutf8


13
Công cụ xuất sắc! Tất cả các giải pháp khác loại bỏ các ký tự không hợp lệ, nhưng giải pháp này khắc phục nó. Tuyệt vời.
giorgio79

4
Bạn đã làm một chức năng tuyệt vời! Trước đây tôi đã làm việc rất nhiều với Nguồn cấp dữ liệu XML và luôn gặp sự cố với mã hóa. Cảm ơn bạn.
Kostanos

5
TÔI MẾN BẠN. Bạn đã tiết kiệm cho tôi HOURS công việc "bloomoin" trên các ký tự UTF8 không hợp lệ. Cảm ơn.
John Ballinger

4
Cái này thật tuyệt. Cảm ơn bạn
EdgeCaseBerg

2
tuyệt vời, tốt lắm! Rất vui vì tôi đã tìm thấy điều này. Tôi ước mình có thể bỏ phiếu với +100 ;-)
Codebeat

61

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ệ.

Xem: Thay thế các ký tự UTF-8 không hợp lệ bằng dấu chấm hỏi, mbstring.substitution_character dường như bị bỏ qua


1
@Alliswell những cái nào? Bạn có thể vui lòng cung cấp một ví dụ?
Frosty Z

chắc chắn,<0x1a>
Alliswell

1
@Alliswell Nếu tôi không nhầm <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/…
Frosty Z

vâng, đó là trường hợp. Cảm ơn, bạn đời!
Alliswell

Trước khi gọi mb convert, tôi phải đặt ký tự thay thế mbstring thành không có 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ả.
cby016,

21

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?

8
Tại sao tên hàm viết hoa toàn bộ? Ơ kìa.
Chris Baker

5
nó là ASCII và thậm chí không gần với những gì câu hỏi mong muốn.
misaxi 12/1213

1
Cái này đã hoạt động. Tôi gặp sự cố khi API Google Maps báo cáo lỗi do 'ký tự không phải UTF-8' trong URL yêu cầu API. Thủ phạm là í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 :)
Valentine Shi

17
$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/


không hiệu quả với tôi. Tôi ước tôi có thể đính kèm dòng đã thử nghiệm, nhưng tiếc là nó có các ký tự không hợp lệ.
Nir O.

3
Xin lỗi, sau một số thử nghiệm nữa, tôi nhận ra rằng điều này không thực sự làm như tôi nghĩ. Tôi hiện đang sử dụng stackoverflow.com/a/8215387/138023
Znarkus

14

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 //IGNOREcờ 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.


Giải thích câu trả lời của bạn có tác dụng gì thay vì kết xuất một đoạn mã.
Tomasz Kowalczyk

3
Tôi đã thử điều này và //IGNOREdườ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.
halfer

Luôn luôn tốt hơn để sử dụng 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 iconvnhư$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
m3nda

@ erm3nda: Chính xác là tôi không nhớ trường hợp sử dụng của mình cho việc này - có thể đã phân tích cú pháp trang web UTF-8 được khai báo với bộ ký tự sai. Cảm ơn vì ghi chú, tôi chắc chắn rằng nó sẽ hữu ích cho một độc giả trong tương lai.
halfer

Vâng, nếu bạn không biết điều gì đó, chỉ cần kiểm tra cho nó và cuối cùng bạn sẽ nhấn phím ;-)
m3nda


6

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

Bạn có ba giải pháp tốt, nhưng không rõ người dùng sẽ chọn như thế nào trong số đó.
Bob Ray

6

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

Trong tất cả các câu trả lời phức tạp ở trên, câu trả lời này đã giúp tôi! Cảm ơn.
Emin Özlem

Tôi bối rối bởi chức năng này. ord()trả về kết quả trong phạm vi 0-255. Gã khổng lồ iftrong 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.
i336_

4

Chào mừng bạn đến với năm 2019 và công cụ /usử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ẽ:

  • Xóa tất cả các ký tự đa byte UTF-8 không hợp lệ bằng mb_convert_encoding
  • Xóa tất cả các ký tự không in được như \r, \x00(NULL-byte) và các ký tự điều khiển khác bằngpreg_replace

phương pháp:

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à \ndò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 \nlà 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

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

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

https://www.tehplayground.com/q5sJ3FOddhv1atpR


Chào mừng năm 2047, nơi php-mbstringkhông được đóng gói trong php theo mặc định.
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'));

2

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


iconvtố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.
m3nda 19/06/16

3
preg_replace không bị phản đối
Oleksii Chekulaiev

1
Bạn hoàn toàn đúng, là ereg_replace(), xin lỗi.
m3nda

2

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_decodesẽ chuyển đổi các ký tự thành dấu chấm hỏi;
str_replacesẽ loại bỏ các dấu chấm hỏi.


Sau khi thử hàng trăm giải pháp, giải pháp duy nhất hiệu quả là của bạn.
Haritsinh Gohil

1

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)
Markus Jarderot

1

Để 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);

0

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, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?"
"Headlight~ Bracket, &#123; 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.


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

nó hoạt động trên dịch vụ của chúng tôi


2
Bạn có thể thêm một số ngữ cảnh để giải thích cách điều này sẽ trả lời câu hỏi, thay vì câu trả lời chỉ có mã.
Arun Vinoth

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.