Phát hiện mã hóa và tạo mọi thứ UTF-8


304

Tôi đang đọc rất nhiều văn bản từ nhiều nguồn cấp RSS khác nhau và chèn chúng vào cơ sở dữ liệu của tôi.

Tất nhiên, có một số mã hóa ký tự khác nhau được sử dụng trong các nguồn cấp dữ liệu, ví dụ UTF-8 và ISO 8859-1.

Thật không may, đôi khi có vấn đề với mã hóa của các văn bản. Thí dụ:

  1. "Ss" trong "Fußball" sẽ trông như thế này trong cơ sở dữ liệu của tôi: "Ÿ". Nếu là "Ÿ", nó được hiển thị chính xác.

  2. Đôi khi, "ß" trong "Fußball" trông như thế này trong cơ sở dữ liệu của tôi: "ß". Sau đó, nó được hiển thị sai, tất nhiên.

  3. Trong các trường hợp khác, "ß" được lưu dưới dạng "ß" - vì vậy không có bất kỳ thay đổi nào. Sau đó, nó cũng được hiển thị sai.

Tôi có thể làm gì để tránh trường hợp 2 và 3?

Làm cách nào tôi có thể tạo mọi thứ cùng mã hóa, tốt nhất là UTF-8? Khi nào tôi phải sử dụng utf8_encode(), khi nào tôi phải sử dụng utf8_decode()(rõ ràng hiệu ứng là gì nhưng khi nào tôi phải sử dụng các chức năng?) Và khi nào tôi phải làm gì với đầu vào?

Làm thế nào để tôi làm cho mọi thứ cùng mã hóa? Có lẽ với chức năng mb_detect_encoding()? Tôi có thể viết một chức năng cho việc này? Vì vậy, vấn đề của tôi là:

  1. Làm thế nào để tôi tìm ra những gì mã hóa văn bản sử dụng?
  2. Làm cách nào để chuyển đổi nó thành UTF-8 - bất kể mã hóa cũ là gì?

Một chức năng như thế này sẽ làm việc?

function correct_encoding($text) {
    $current_encoding = mb_detect_encoding($text, 'auto');
    $text = iconv($current_encoding, 'UTF-8', $text);
    return $text;
}

Tôi đã thử nó, nhưng nó không hoạt động. Có gì sai với nó?


36
"" Ss "trong" Fußball "sẽ trông như thế này trong cơ sở dữ liệu của tôi:" Ÿ ".". Không, nó sẽ trông giống như ß. Hãy chắc chắn rằng bạn đối chiếu và kết nối được thiết lập chính xác. Nếu không sắp xếp và tìm kiếm sẽ bị phá vỡ cho bạn.
Giàu Bradshaw

5
Cơ sở dữ liệu của bạn được thiết lập xấu. Nếu bạn muốn lưu trữ nội dung Unicode, chỉ cần cấu hình nó cho điều đó. Vì vậy, thay vì cố gắng giải quyết vấn đề trong mã PHP của bạn, trước tiên bạn nên sửa cơ sở dữ liệu.
heo

2
SỬ DỤNG: $ from = mb_detect_encoding ($ text); $ text = mb_convert_encoding ($ text, 'UTF-8', $ từ);
Thông

Câu trả lời:


363

Nếu bạn áp dụng utf8_encode()cho một chuỗi UTF-8, nó sẽ trả về đầu ra UTF-8 bị cắt xén.

Tôi đã thực hiện một chức năng 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 phải biết mã hóa chuỗi của bạn là gì. Nó có thể là Latin1 ( ISO 8859-1) , Windows-1252 hoặc UTF-8 hoặc chuỗi có thể có sự pha trộn của chúng. Encoding::toUTF8()sẽ chuyển đổi mọi thứ thành UTF-8.

Tôi đã làm điều đó bởi vì một dịch vụ đã cung cấp cho tôi một nguồn cấp dữ liệu bị rối tung, trộn UTF-8 và Latin1 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($utf8_or_latin1_or_mixed_string);

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

Tải xuống:

https://github.com/neitanod/forceutf8

Tôi đã bao gồm một chức năng khác, Encoding::fixUFT8()sẽ sửa mọi chuỗi UTF-8 bị cắt xé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 ra:

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 đã chuyển đổi hàm ( forceUTF8) thành một nhóm các hàm tĩnh trên một lớp được gọi Encoding. Các chức năng mới là Encoding::toUTF8().


1
Chà, nếu bạn nhìn vào mã, fixUTF8 chỉ cần gọi ForceUTF8 một lần nữa cho đến khi chuỗi được trả về không thay đổi. Một cuộc gọi đến fixUTF8 () mất ít nhất hai lần thời gian của cuộc gọi đến forceUTF8 (), do đó, nó ít hiệu quả hơn nhiều. Tôi đã tạo fixUTF8 () chỉ để tạo một chương trình dòng lệnh sẽ sửa các tệp "bị mã hóa", nhưng trong một môi trường trực tiếp hiếm khi cần thiết.
Sebastián Grignoli

3
Làm thế nào điều này chuyển đổi các ký tự không phải UTF8 thành UTF8, mà không biết mã hóa các ký tự không hợp lệ bắt đầu bằng gì?
philfreo

4
Nó giả định ISO-8859-1, câu trả lời đã nói lên điều này. Sự khác biệt duy nhất giữa forceUTF8 () và utf8_encode () là ForceUTF8 () nhận ra các ký tự UTF8 và giữ chúng không thay đổi.
Sebastián Grignoli

28
"Bạn không cần biết mã hóa chuỗi của bạn là gì." - Tôi rất không đồng ý. Đoán và thử có thể hoạt động, nhưng bạn sẽ sớm gặp phải các trường hợp không phù hợp.
lừa dối

4
Tôi hoàn toàn đồng ý. Trên thực tế, tôi không có ý nói rằng như một quy tắc chung, chỉ cần giải thích rằng lớp học này có thể giúp bạn nếu đó là tình huống bạn tình
cờ

74

Trước tiên bạn phải phát hiện mã hóa nào đã được sử dụng. Khi bạn phân tích nguồn cấp RSS (có thể qua HTTP), bạn nên đọc mã hóa từ charsettham số của trường Content-Typetiêu đề HTTP . Nếu nó không có mặt, hãy đọc mã hóa từ encodingthuộc tính của hướng dẫn xử lý XML . Nếu điều đó cũng bị thiếu, hãy sử dụng UTF-8 như được định nghĩa trong thông số kỹ thuật .


Chỉnh sửa    Đây là những gì tôi có thể sẽ làm:

Tôi sẽ sử dụng cURL để gửi và lấy phản hồi. Điều đó cho phép bạn đặt các trường tiêu đề cụ thể và cũng tìm nạp tiêu đề phản hồi. Sau khi tìm nạp phản hồi, bạn phải phân tích phản hồi HTTP và chia nó thành tiêu đề và phần thân. Sau đó, tiêu đề sẽ chứa trường Content-Typetiêu đề chứa loại MIME và (hy vọng) charsettham số với bảng mã / bộ ký tự cũng vậy. Nếu không, chúng tôi sẽ phân tích XML PI cho sự hiện diện của encodingthuộc tính và lấy mã hóa từ đó. Nếu điều đó cũng bị thiếu, thông số kỹ thuật XML xác định sử dụng UTF-8 làm mã hóa.

$url = 'http://www.lr-online.de/storage/rss/rss/sport.xml';

$accept = array(
    'type' => array('application/rss+xml', 'application/xml', 'application/rdf+xml', 'text/xml'),
    'charset' => array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'byte2be', 'byte2le', 'byte4be', 'byte4le', 'BASE64', 'UUENCODE', 'HTML-ENTITIES', 'Quoted-Printable', '7bit', '8bit'))
);
$header = array(
    'Accept: '.implode(', ', $accept['type']),
    'Accept-Charset: '.implode(', ', $accept['charset']),
);
$encoding = null;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$response = curl_exec($curl);
if (!$response) {
    // error fetching the response
} else {
    $offset = strpos($response, "\r\n\r\n");
    $header = substr($response, 0, $offset);
    if (!$header || !preg_match('/^Content-Type:\s+([^;]+)(?:;\s*charset=(.*))?/im', $header, $match)) {
        // error parsing the response
    } else {
        if (!in_array(strtolower($match[1]), array_map('strtolower', $accept['type']))) {
            // type not accepted
        }
        $encoding = trim($match[2], '"\'');
    }
    if (!$encoding) {
        $body = substr($response, $offset + 4);
        if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $body, $match)) {
            $encoding = trim($match[1], '"\'');
        }
    }
    if (!$encoding) {
        $encoding = 'utf-8';
    } else {
        if (!in_array($encoding, array_map('strtolower', $accept['charset']))) {
            // encoding not accepted
        }
        if ($encoding != 'utf-8') {
            $body = mb_convert_encoding($body, 'utf-8', $encoding);
        }
    }
    $simpleXML = simplexml_load_string($body, null, LIBXML_NOERROR);
    if (!$simpleXML) {
        // parse error
    } else {
        echo $simpleXML->asXML();
    }
}

Cảm ơn. Điều này sẽ dễ dàng. Nhưng nó sẽ thực sự làm việc? Thường có các mã hóa sai được đưa ra trong các tiêu đề HTTP hoặc trong các thuộc tính của XML.
caw

25
Một lần nữa: Đó không phải là vấn đề của bạn. Các tiêu chuẩn được thiết lập để tránh những rắc rối như vậy. Nếu những người khác không theo dõi họ, đó là vấn đề của họ, không phải của bạn.
Gumbo

Ok, tôi nghĩ rằng cuối cùng bạn đã thuyết phục tôi. :)
caw

Cảm ơn mã. Nhưng tại sao không chỉ đơn giản là sử dụng này? paste.bradleygill.com/index.php?paste_id=9651 Mã của bạn phức tạp hơn nhiều, có gì tốt hơn với nó?
caw

Chà, trước tiên, bạn đang thực hiện hai yêu cầu, một cho tiêu đề HTTP và một cho dữ liệu. Thứ hai, bạn đang tìm kiếm bất kỳ xuất hiện của charset=encoding=và không chỉ tại các vị trí thích hợp. Và thứ ba, bạn không kiểm tra xem mã hóa được khai báo có được chấp nhận hay không.
Gumbo

39

Phát hiện mã hóa là khó.

mb_detect_encodinghoạt động bằng cách đoán, dựa trên một số ứng cử viên mà bạn vượt qua nó. Trong một số mã hóa, các chuỗi byte nhất định không hợp lệ, do đó nó có thể phân biệt giữa các ứng cử viên khác nhau. Thật không may, có rất nhiều mã hóa, trong đó các byte giống nhau là hợp lệ (nhưng khác nhau). Trong những trường hợp này, không có cách nào để xác định mã hóa; Bạn có thể thực hiện logic của riêng bạn để đoán trong những trường hợp này. Ví dụ: dữ liệu đến từ một trang web của Nhật Bản có thể có nhiều khả năng có mã hóa tiếng Nhật hơn.

Miễn là bạn chỉ giao dịch với các ngôn ngữ Tây Âu, ba mã hóa chính cần xem xét là utf-8, iso-8859-1cp-1252. Vì đây là mặc định cho nhiều nền tảng, nên chúng cũng có khả năng bị báo cáo sai nhất. Ví dụ. nếu mọi người sử dụng các bảng mã khác nhau, họ có thể sẽ thẳng thắn về nó, vì nếu không thì phần mềm của họ sẽ bị hỏng rất thường xuyên. Do đó, một chiến lược tốt là tin tưởng vào nhà cung cấp, trừ khi mã hóa được báo cáo là một trong ba. Bạn vẫn nên kiểm tra lại rằng nó thực sự hợp lệ, bằng cách sử dụng mb_check_encoding(lưu ý rằng hợp lệ không giống như hiện tại - cùng một đầu vào có thể hợp lệ cho nhiều mã hóa). Nếu đó là một trong số đó, thì bạn có thể sử dụngmb_detect_encodingđể phân biệt giữa chúng. May mắn thay, đó là khá quyết định; Bạn chỉ cần sử dụng trình tự phát hiện thích hợp, đó là UTF-8,ISO-8859-1,WINDOWS-1252.

Khi bạn đã phát hiện mã hóa, bạn cần chuyển đổi nó thành biểu diễn bên trong của bạn ( UTF-8là lựa chọn lành mạnh duy nhất). Hàm utf8_encodebiến đổi ISO-8859-1thành UTF-8, vì vậy nó chỉ có thể được sử dụng cho loại đầu vào cụ thể đó. Đối với các bảng mã khác, sử dụng mb_convert_encoding.


Cảm ơn rât nhiều! Điều gì tốt hơn: mb-convert-mã hóa () hoặc iconv ()? Tôi không biết sự khác biệt là gì. Vâng, tôi sẽ chỉ phải phân tích các ngôn ngữ Tây Âu, đặc biệt là tiếng Anh, tiếng Đức và tiếng Pháp.
caw

7
Tôi vừa thấy: mb-dò-mã hóa () vô dụng. Nó chỉ hỗ trợ UTF-8, UTF-7, ASCII, EUC-JP, SJIS, eucJP-win, SJIS-win, JIS và ISO-2022-JP. Những cái quan trọng nhất đối với tôi, ISO-8859-1 và WINDOWS-1252, không được hỗ trợ. Vì vậy, tôi không thể sử dụng mb-dò-mã hóa ().
caw

1
Ôi, bạn nói đúng. Đã được một thời gian kể từ khi tôi sử dụng nó. Sau đó, bạn sẽ phải viết mã phát hiện của riêng mình hoặc sử dụng tiện ích bên ngoài. UTF-8 có thể được xác định khá đáng tin cậy, bởi vì các chuỗi thoát của nó khá đặc trưng. wp-1252 và iso-8859-1 có thể được phân biệt vì wp-1252 có thể chứa các byte bất hợp pháp trong iso-8859-1. Sử dụng Wikipedia để lấy thông tin chi tiết hoặc xem phần bình luận của php.net, dưới các chức năng liên quan đến bộ ký tự khác nhau.
troelskn

Tôi nghĩ bạn có thể phân biệt các bảng mã khác nhau khi bạn nhìn vào các hình thức mà các bài hát đặc biệt xuất hiện trong: "ß" của Đức xuất hiện dưới các hình thức khác nhau: Đôi khi "Ÿ", đôi khi "ß" và đôi khi "ß". Tại sao?
caw

Có, nhưng sau đó bạn cần biết nội dung của chuỗi trước khi so sánh nó, và loại đó đánh bại mục đích ngay từ đầu. Chữ ß của Đức xuất hiện khác nhau vì nó có các giá trị khác nhau trong các bảng mã khác nhau. Một số ký tự tình cờ được thể hiện theo cùng một cách trong các bảng mã khác nhau (ví dụ: tất cả các ký tự trong bộ ký tự ascii được mã hóa theo cùng một cách trong utf-8, iso-8859- * và wp-1252), miễn là bạn sử dụng chỉ những nhân vật đó, tất cả đều giống nhau Đó là lý do tại sao chúng đôi khi được gọi là tương thích ascii.
troelskn

14

Một cách thực sự hay để thực hiện một isUTF8chức năng có thể được tìm thấy trên php.net :

function isUTF8($string) {
    return (utf8_encode(utf8_decode($string)) == $string);
}

16
Thật không may, điều này chỉ hoạt động khi chuỗi chỉ bao gồm các ký tự được bao gồm trong ISO-8859-1. Nhưng điều này có thể hoạt động: @iconv ('utf-8', 'utf-8 // IGNORE', $ str) == $ str
Christian Davén

@Christian: Thật vậy, đó cũng là những gì các tác giả của MySQL hiệu suất cao khuyên dùng.
Alix Axel

1
Nó không hoạt động chính xác: echo (int) isUTF8 ('z'); Tiếng vang số 1 (int) isUTF8 (NULL); # 1
Yousha Aleayoub

1
Mặc dù không hoàn hảo, tôi nghĩ rằng đây là một cách hay để thực hiện kiểm tra UTF-8 sơ sài.
Mateng

1
mb_check_encoding($string, 'UTF-8')
lừa dối

13

Chiếc áo này liệt kê một số cảnh báo phổ biến liên quan đến việc xử lý UTF-8 trong PHP: http://developer.loftdigital.com/blog/php-utf-8-chcoateet

Hàm này phát hiện các ký tự đa nhân trong một chuỗi cũng có thể chứng minh hữu ích ( nguồn ):


function detectUTF8($string)
{
    return preg_match('%(?:
        [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
        |\xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
        |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
        |\xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
        |\xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
        |[\xF1-\xF3][\x80-\xBF]{3}         # planes 4-15
        |\xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
        )+%xs', 
    $string);
}


2
Tôi nghĩ rằng nó không hoạt động chính xác: echo phát hiệnUTF8 ('3٣3'); # 1
Yousha Aleayoub

10

Một chút ngẩng đầu lên. Bạn nói rằng "ß" phải được hiển thị là "Ÿ" trong cơ sở dữ liệu của bạn.

Điều này có thể là do bạn đang sử dụng cơ sở dữ liệu với mã hóa ký tự Latin-1 hoặc có thể kết nối PHP-MySQL của bạn bị đặt sai, điều này là, P tin rằng MySQL của bạn được đặt để sử dụng UTF-8, vì vậy nó sẽ gửi dữ liệu dưới dạng UTF-8 , nhưng MySQL của bạn tin rằng PHP đang gửi dữ liệu được mã hóa theo tiêu chuẩn ISO 8859-1, do đó, một lần nữa nó có thể cố mã hóa dữ liệu đã gửi của bạn dưới dạng UTF-8, gây ra loại rắc rối này.

Hãy xem mysql_set_charset . Nó có thể giúp bạn.


4

Mã hóa của bạn trông giống như bạn được mã hóa thành UTF-8 hai lần ; nghĩa là, từ một số mã hóa khác, thành UTF-8, và một lần nữa vào UTF-8. Như thể bạn có ISO 8859-1, được chuyển đổi từ ISO 8859-1 sang UTF-8 và xử lý chuỗi mới là ISO 8859-1 cho một chuyển đổi khác thành UTF-8.

Đây là một số mã giả về những gì bạn đã làm:

$inputstring = getFromUser();
$utf8string = iconv($current_encoding, 'utf-8', $inputstring);
$flawedstring = iconv($current_encoding, 'utf-8', $utf8string);

Bạn nên thử:

  1. phát hiện mã hóa bằng cách sử dụng mb_detect_encoding()hoặc bất cứ điều gì bạn muốn sử dụng
  2. nếu là UTF-8, hãy chuyển đổi thành ISO 8859-1 và lặp lại bước 1
  3. cuối cùng, chuyển đổi trở lại thành UTF-8

Điều đó có nghĩa là trong chuyển đổi "giữa", bạn đã sử dụng ISO 8859-1. Nếu bạn đã sử dụng Windows-1252, sau đó chuyển đổi thành Windows-1252 (latin1). Mã hóa nguồn ban đầu không quan trọng; một trong những bạn đã sử dụng trong chuyển đổi thiếu sót, thứ hai là.

Đây là dự đoán của tôi về những gì đã xảy ra; có rất ít thứ khác bạn có thể thực hiện để có được bốn byte thay cho một byte ASCII mở rộng.

Ngôn ngữ Đức cũng sử dụng ISO 8859-2Windows-1250 (Latin-2).


3

Điều thú vị về mb_detect_encodingmb_convert_encodinglà thứ tự của các bảng mã mà bạn đề xuất có vấn đề:

// $input is actually UTF-8

mb_detect_encoding($input, "UTF-8", "ISO-8859-9, UTF-8");
// ISO-8859-9 (WRONG!)

mb_detect_encoding($input, "UTF-8", "UTF-8, ISO-8859-9");
// UTF-8 (OK)

Vì vậy, bạn có thể muốn sử dụng một thứ tự cụ thể khi chỉ định mã hóa dự kiến. Tuy nhiên, hãy nhớ rằng điều này không phải là hoàn hảo.


2
Điều này xảy ra vì trong thực tế, ISO-8859-9 sẽ chấp nhận mọi đầu vào nhị phân. Điều tương tự cũng xảy ra với Windows-1252 và bạn bè. Trước tiên, bạn phải kiểm tra các mã hóa có thể không chấp nhận đầu vào.
Mikko Rantalainen

@MikkoRantalainen, vâng, tôi đoán phần này của các tài liệu nói điều gì đó tương tự: php.net/manual/en/feft.mb-detect-order.php#example-2985
Halil zgür

Xem xét rằng đặc tả WHATWG HTML định nghĩa Windows 1252 là mã hóa mặc định, sẽ khá an toàn khi giả định if ($input_is_not_UTF8) $input_is_windows1252 = true;. Xem thêm: html.spec.whatwg.org/multipage/ Kẻ
Mikko Rantalainen

3

Bạn cần kiểm tra bộ ký tự trên đầu vào vì các phản hồi có thể được mã hóa bằng các bảng mã khác nhau.

Tôi buộc tất cả nội dung được gửi vào UTF-8 bằng cách thực hiện phát hiện và dịch bằng chức năng sau:

function fixRequestCharset()
{
  $ref = array(&$_GET, &$_POST, &$_REQUEST);
  foreach ($ref as &$var)
  {
    foreach ($var as $key => $val)
    {
      $encoding = mb_detect_encoding($var[$key], mb_detect_order(), true);
      if (!$encoding)
        continue;
      if (strcasecmp($encoding, 'UTF-8') != 0)
      {
        $encoding = iconv($encoding, 'UTF-8', $var[$key]);
        if ($encoding === false)
          continue;
        $var[$key] = $encoding;
      }
    }
  }
}

Thường trình đó sẽ biến tất cả các biến PHP xuất phát từ máy chủ từ xa thành UTF-8.

Hoặc bỏ qua giá trị nếu mã hóa không thể được phát hiện hoặc chuyển đổi.

Bạn có thể tùy chỉnh nó theo nhu cầu của bạn.

Chỉ cần gọi nó trước khi sử dụng các biến.


mục đích của việc sử dụng mb_detect_order () mà không có trong danh sách mã hóa là gì?
giorgio79

Mục đích là để trả về mảng mã hóa được định cấu hình hệ thống được định nghĩa trong php.ini được sử dụng. Điều này được mb_detect_encoding yêu cầu để điền tham số thứ ba.
cavila

2

Làm việc mã hóa ký tự của các nguồn cấp RSS có vẻ phức tạp . Ngay cả các trang web bình thường cũng thường bỏ qua hoặc nói dối về mã hóa của chúng.

Vì vậy, bạn có thể thử sử dụng đúng cách để phát hiện mã hóa và sau đó quay lại một số hình thức tự động phát hiện (đoán).


Tôi không muốn đọc mã hóa từ thông tin nguồn cấp dữ liệu. Vì vậy, nó là bằng nhau nếu thông tin thức ăn là sai. Tôi muốn phát hiện mã hóa từ văn bản.
caw

@ marco92w: Không phải vấn đề của bạn nếu mã hóa được khai báo là sai. Tiêu chuẩn chưa được thiết lập cho vui.
Gumbo

1
@Gumbo: nhưng nếu bạn đang làm việc trong thế giới thực, bạn phải có khả năng xử lý những việc như mã hóa khai báo không chính xác. Vấn đề là rất khó đoán (chính xác) mã hóa chỉ từ một số văn bản. Các tiêu chuẩn là tuyệt vời, nhưng nhiều (hầu hết?) Các trang / nguồn cấp dữ liệu ngoài đó không tuân thủ chúng.
Kevin ORourke

@Kevin ORourke: Chính xác, đúng. Đó là vấn đề của tôi. @Gumbo: Vâng, đó là vấn đề của tôi. Tôi muốn đọc các nguồn cấp dữ liệu và tổng hợp chúng. Vì vậy, tôi phải sửa các mã hóa sai.
caw

@ marco92w: Nhưng bạn không thể sửa mã hóa nếu bạn không biết mã hóa chính xác và mã hóa hiện tại. Và đó là những gì charset/ encodingkhai báo nếu: mô tả mã hóa dữ liệu được mã hóa.
Gumbo

2

Tôi biết đây là một câu hỏi cũ hơn, nhưng tôi nghĩ rằng một câu trả lời hữu ích không bao giờ bị tổn thương. Tôi đã gặp vấn đề với mã hóa giữa một ứng dụng máy tính để bàn, các biến SQLite và GET / POST. Một số sẽ ở UTF-8, một số sẽ ở ASCII và về cơ bản mọi thứ sẽ bị rối tung khi các nhân vật nước ngoài tham gia.

Đây là giải pháp của tôi. Nó xóa các GET / POST / REQUEST của bạn (Tôi đã bỏ qua cookie, nhưng bạn có thể thêm chúng nếu muốn) trên mỗi lần tải trang trước khi xử lý. Nó hoạt động tốt trong một tiêu đề. PHP sẽ đưa ra các cảnh báo nếu nó không thể tự động phát hiện mã hóa nguồn, vì vậy những cảnh báo này bị loại bỏ với @ 's.

//Convert everything in our vars to UTF-8 for playing nice with the database...
//Use some auto detection here to help us not double-encode...
//Suppress possible warnings with @'s for when encoding cannot be detected
try
{
    $process = array(&$_GET, &$_POST, &$_REQUEST);
    while (list($key, $val) = each($process)) {
        foreach ($val as $k => $v) {
            unset($process[$key][$k]);
            if (is_array($v)) {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = $v;
                $process[] = &$process[$key][@mb_convert_encoding($k,'UTF-8','auto')];
            } else {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = @mb_convert_encoding($v,'UTF-8','auto');
            }
        }
    }
    unset($process);
}
catch(Exception $ex){}

Cảm ơn câu trả lời, vui vẻ. Hàm mb_convert_encoding () là những gì chúng ta đã có ở đây, phải không? ;) Vì vậy, điều mới duy nhất trong câu trả lời của bạn là các vòng lặp để thay đổi mã hóa trong tất cả các biến.
caw

2

Tôi đã kiểm tra các giải pháp để mã hóa từ rất lâu đời và trang này có lẽ là kết luận của nhiều năm tìm kiếm! Tôi đã thử nghiệm một số đề xuất mà bạn đề cập và đây là ghi chú của tôi:

Đây là chuỗi thử nghiệm của tôi:

đây là một chuỗi "wròng wrìtten" tôi đã đến pù 'sòme' đặc biệt để xem thèm, convertèd bởi fùnctìon !! & đó là nó!

Tôi thực hiện một INSERT để lưu chuỗi này trên cơ sở dữ liệu trong trường được đặt là utf8_general_ci

Bộ ký tự của trang của tôi là UTF-8.

Nếu tôi thực hiện một INSERT giống như vậy, trong cơ sở dữ liệu của tôi, tôi có một số nhân vật có thể đến từ Sao Hỏa ...

Vì vậy, tôi cần chuyển đổi chúng thành một số UTF-8 "lành mạnh". Tôi đã thử utf8_encode(), nhưng vẫn có những ký tự ngoài hành tinh đang xâm chiếm cơ sở dữ liệu của tôi ...

Vì vậy, tôi đã cố gắng sử dụng chức năng forceUTF8được đăng trên số 8, nhưng trong cơ sở dữ liệu, chuỗi được lưu trông như thế này:

đây là một chuỗi "wròng wrìtten" nhưng tôi không phải là người đặc biệt để xem họ, chuyển đổi bởi fùnctìon !! & đó là nó!

Vì vậy, việc thu thập thêm một số thông tin trên trang này và hợp nhất chúng với các thông tin khác trên các trang khác tôi đã giải quyết vấn đề của mình với giải pháp này:

$finallyIDidIt = mb_convert_encoding(
  $string,
  mysql_client_encoding($resourceID),
  mb_detect_encoding($string)
);

Bây giờ trong cơ sở dữ liệu của tôi, tôi có chuỗi của tôi với mã hóa chính xác.

LƯU Ý: Chỉ lưu ý để chăm sóc là trong chức năng mysql_client_encoding! Bạn cần được kết nối với cơ sở dữ liệu, bởi vì hàm này muốn ID tài nguyên làm tham số.

Nhưng tốt, tôi chỉ thực hiện việc mã hóa lại trước INSERT của mình để đối với tôi nó không phải là vấn đề.


1
Tại sao bạn không chỉ sử dụng UTF-8mã hóa máy khách cho mysql ngay từ đầu? Không cần chuyển đổi thủ công theo cách này
Esailija

2

Thật đơn giản: khi bạn nhận được thứ gì đó không phải là UTF-8, bạn phải mã hóathành UTF-8.

Vì vậy, khi bạn đang tìm nạp một nguồn cấp dữ liệu nhất định, ISO 8859-1 sẽ phân tích nó utf8_encode.

Tuy nhiên, nếu bạn đang tìm nạp nguồn cấp UTF-8, bạn không cần phải làm gì cả.


Cảm ơn! OK, tôi có thể tìm hiểu cách thức ăn được mã hóa bằng cách sử dụng mb-dò-mã hóa (), phải không? Nhưng tôi có thể làm gì nếu nguồn cấp dữ liệu là ASCII? utf8-encode () ist chỉ dành cho ISO-8859-1 đến UTF-8, phải không?
kêu

ASCII là tập hợp con của ISO-8859-1 VÀ UTF-8, vì vậy sử dụng mã hóa utf8 () sẽ không tạo ra thay đổi - NẾU thực sự chỉ là ASCII
Michael Borgwardt

Vì vậy, tôi luôn có thể sử dụng utf8_encode nếu không phải là UTF-8? Điều này sẽ thực sự dễ dàng. Văn bản được ASCII theo mb-dò-mã hóa () có chứa "& # 228;". Đây có phải là một nhân vật ASCII? Hay là HTML?
caw

Đó là HTML. Trên thực tế, đó là mã hóa nên khi bạn in nó trong một trang nhất định, nó hiển thị ok. Nếu bạn muốn, trước tiên bạn có thể ut8_encode () sau đó html_entity_decode ().
Seb

1
Ký tự ß được mã hóa theo UTF-8 với chuỗi byte 0xC39F. Giải thích với Windows-1252, chuỗi đó đại diện cho hai ký tự  (0xC3) và (0x9F). Và nếu bạn mã hóa lại chuỗi byte này một lần nữa bằng UTF-8, bạn sẽ nhận được 0xC383 0xC29F, đại diện cho ƒƒ trong Windows-1252. Vì vậy, sai lầm của bạn là xử lý dữ liệu được mã hóa UTF-8 này như một thứ gì đó có mã hóa khác với UTF-8. Chuỗi byte này được trình bày dưới dạng ký tự bạn nhìn thấy chỉ là vấn đề giải thích. Nếu bạn sử dụng một bảng mã / bảng mã khác, có thể bạn sẽ thấy các ký tự khác.
Gumbo

1

php.net/mb_detect_encoding

echo mb_detect_encoding($str, "auto");

hoặc là

echo mb_detect_encoding($str, "UTF-8, ASCII, ISO-8859-1");

Tôi thực sự không biết kết quả là gì, nhưng tôi khuyên bạn chỉ nên lấy một số nguồn cấp dữ liệu của mình với các bảng mã khác nhau và thử xem có mb_detect_encodinghoạt động hay không.

cập nhật
tự động là viết tắt của "ASCII, JIS, UTF-8, EUC-JP, SJIS". nó trả về bộ ký tự được phát hiện, mà bạn có thể sử dụng để chuyển đổi chuỗi thành utf-8 với iconv .

<?php
function convertToUTF8($str) {
    $enc = mb_detect_encoding($str);

    if ($enc && $enc != 'UTF-8') {
        return iconv($enc, 'UTF-8', $str);
    } else {
        return $str;
    }
}
?>

Tôi đã không kiểm tra nó, vì vậy không có gì đảm bảo. và có lẽ có một cách đơn giản hơn.


Cảm ơn bạn. Sự khác biệt giữa 'tự động' và 'UTF-8, ASCII, ISO-8859-1' là đối số thứ hai là gì? Liệu 'tự động' có tính năng mã hóa nhiều hơn? Sau đó, sẽ tốt hơn nếu sử dụng 'tự động', phải không? Nếu nó thực sự hoạt động mà không có bất kỳ lỗi nào thì tôi chỉ phải thay đổi "ASCII" hoặc "ISO-8859-1" thành "UTF-8". Làm sao?
caw

2
Chức năng của bạn không hoạt động tốt trong mọi trường hợp. Đôi khi tôi gặp lỗi: Lưu ý: iconv (): Đã phát hiện một ký tự không hợp lệ trong chuỗi đầu vào trong ...
caw

1

@harpax mà làm việc cho tôi. Trong trường hợp của tôi, điều này là đủ tốt:

if (isUTF8($str)) { 
    echo $str; 
}
else
{
    echo iconv("ISO-8859-1", "UTF-8//TRANSLIT", $str);
}

0

Sau khi sắp xếp các tập lệnh php của bạn, đừng quên nói với mysql bạn đang vượt qua bộ ký tự nào và muốn đọc lại.

Ví dụ: thiết lập bộ ký tự utf8

Truyền dữ liệu utf8 vào bảng latin1 trong phiên I / O Latin1 sẽ mang lại cho những chú chim khó chịu. Tôi thấy điều này mỗi ngày trong các cửa hàng thương mại điện tử. Trở lại và thứ tư có vẻ đúng. Nhưng phpmyadmin sẽ cho thấy sự thật. Bằng cách thông báo cho mysql bộ ký tự bạn đang chuyển, nó sẽ xử lý việc chuyển đổi dữ liệu mysql cho bạn.

Làm thế nào để khôi phục dữ liệu mysql bị xáo trộn hiện có là một chủ đề khác để thảo luận. :)


0

Phiên bản này dành cho ngôn ngữ tiếng Đức nhưng bạn có thể sửa đổi $ CHARSETS và $ TESTCHARS

class CharsetDetector
{
private static $CHARSETS = array(
"ISO_8859-1",
"ISO_8859-15",
"CP850"
);
private static $TESTCHARS = array(
"€",
"ä",
"Ä",
"ö",
"Ö",
"ü",
"Ü",
"ß"
);
public static function convert($string)
{
    return self::__iconv($string, self::getCharset($string));
}
public static function getCharset($string)
{
    $normalized = self::__normalize($string);
    if(!strlen($normalized))return "UTF-8";
    $best = "UTF-8";
    $charcountbest = 0;
    foreach (self::$CHARSETS as $charset) {
        $str = self::__iconv($normalized, $charset);
        $charcount = 0;
        $stop   = mb_strlen( $str, "UTF-8");

        for( $idx = 0; $idx < $stop; $idx++)
        {
            $char = mb_substr( $str, $idx, 1, "UTF-8");
            foreach (self::$TESTCHARS as $testchar) {

                if($char == $testchar)
                {

                    $charcount++;
                    break;
                }
            }
        }
        if($charcount>$charcountbest)
        {
            $charcountbest=$charcount;
            $best=$charset;
        }
        //echo $text."<br />";
    }
    return $best;
}
private static function __normalize($str)
{

$len = strlen($str);
$ret = "";
for($i = 0; $i < $len; $i++){
    $c = ord($str[$i]);
    if ($c > 128) {
        if (($c > 247)) $ret .=$str[$i];
        elseif ($c > 239) $bytes = 4;
        elseif ($c > 223) $bytes = 3;
        elseif ($c > 191) $bytes = 2;
        else $ret .=$str[$i];
        if (($i + $bytes) > $len) $ret .=$str[$i];
        $ret2=$str[$i];
        while ($bytes > 1) {
            $i++;
            $b = ord($str[$i]);
            if ($b < 128 || $b > 191) {$ret .=$ret2; $ret2=""; $i+=$bytes-1;$bytes=1; break;}
            else $ret2.=$str[$i];
            $bytes--;
        }
    }
}
return $ret; 
}
private static function __iconv($string, $charset)
{
    return iconv ( $charset, "UTF-8" , $string );
}
}


0

Nhận mã hóa từ các tiêu đề và chuyển đổi nó thành utf-8.

$post_url='http://website.domain';

/// Get headers ////////////////////////////////////////////////////////////
function get_headers_curl($url) 
{ 
    $ch = curl_init(); 

    curl_setopt($ch, CURLOPT_URL,            $url); 
    curl_setopt($ch, CURLOPT_HEADER,         true); 
    curl_setopt($ch, CURLOPT_NOBODY,         true); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($ch, CURLOPT_TIMEOUT,        15); 

    $r = curl_exec($ch); 
    return $r; 
}
$the_header = get_headers_curl($post_url);
/// check for redirect /////////////////////////////////////////////////
if (preg_match("/Location:/i", $the_header)) {
    $arr = explode('Location:', $the_header);
    $location = $arr[1];

    $location=explode(chr(10), $location);
    $location = $location[0];

$the_header = get_headers_curl(trim($location));
}
/// Get charset /////////////////////////////////////////////////////////////////////
if (preg_match("/charset=/i", $the_header)) {
    $arr = explode('charset=', $the_header);
    $charset = $arr[1];

    $charset=explode(chr(10), $charset);
    $charset = $charset[0];
    }
///////////////////////////////////////////////////////////////////////////////
// echo $charset;

if($charset && $charset!='UTF-8') { $html = iconv($charset, "UTF-8", $html); }

0

Ÿlà Mojibake cho ß. Trong cơ sở dữ liệu của bạn, bạn có thể có hex

DF if the column is "latin1",
C39F if the column is utf8 -- OR -- it is latin1, but "double-encoded"
C383C5B8 if double-encoded into a utf8 column

Bạn không nên sử dụng bất kỳ chức năng mã hóa / giải mã nào trong PHP; thay vào đó, bạn nên thiết lập cơ sở dữ liệu và kết nối với nó một cách chính xác.

Nếu MySQL có liên quan, hãy xem: Rắc rối với các ký tự utf8; những gì tôi thấy không phải là những gì tôi lưu trữ


0

Tôi tìm giải pháp tại đây http://deer.org.ua/2009/10/06/1/

class Encoding
{
    /**
     * http://deer.org.ua/2009/10/06/1/
     * @param $string
     * @return null
     */
    public static function detect_encoding($string)
    {
        static $list = ['utf-8', 'windows-1251'];

        foreach ($list as $item) {
            try {
                $sample = iconv($item, $item, $string);
            } catch (\Exception $e) {
                continue;
            }
            if (md5($sample) == md5($string)) {
                return $item;
            }
        }
        return null;
    }
}

$content = file_get_contents($file['tmp_name']);
$encoding = Encoding::detect_encoding($content);
if ($encoding != 'utf-8') {
    $result = iconv($encoding, 'utf-8', $content);
} else {
    $result = $content;
}

Tôi nghĩ rằng @ là quyết định tồi và thực hiện một số thay đổi đối với giải pháp từ Deer.org.ua;


0

Câu trả lời được bình chọn nhiều nhất không hoạt động. Đây là của tôi và hy vọng nó sẽ giúp.

function toUTF8($raw) {
    try{
        return mb_convert_encoding($raw, "UTF-8", "auto"); 
    }catch(\Exception $e){
        return mb_convert_encoding($raw, "UTF-8", "GBK"); 
    }
}

1
Bạn có cái nhìn sâu sắc tại sao, hoặc các tập tin của bạn khác nhau như thế nào không? Những phần không làm việc cho bạn? Ví dụ: Chữ Uppercase tiếng Đức không chuyển đổi chính xác. Tò mò, "GBK" là gì?
SherylHohman

-1

Khi bạn cố gắng xử lý nhiều ngôn ngữ như tiếng Nhật và tiếng Hàn, bạn có thể gặp rắc rối. mb_convert_encoding với tham số 'auto' không hoạt động tốt. Đặt mb_detect_order ('ASCII, UTF-8, JIS, EUC-JP, SJIS, EUC-KR, UHC') sẽ không giúp ích vì nó sẽ phát hiện sai EUC- *.

Tôi đã kết luận rằng miễn là các chuỗi đầu vào đến từ HTML, thì nó nên sử dụng 'bộ ký tự' trong một phần tử meta. Tôi sử dụng Trình phân tích cú pháp DOM đơn giản vì nó hỗ trợ HTML không hợp lệ.

Đoạn trích dưới đây trích xuất phần tử tiêu đề từ một trang web. Nếu bạn muốn chuyển đổi toàn bộ trang, thì bạn có thể muốn xóa một số dòng.

<?php
require_once 'simple_html_dom.php';

echo convert_title_to_utf8(file_get_contents($argv[1])), PHP_EOL;

function convert_title_to_utf8($contents)
{
    $dom = str_get_html($contents);
    $title = $dom->find('title', 0);
    if (empty($title)) {
        return null;
    }
    $title = $title->plaintext;
    $metas = $dom->find('meta');
    $charset = 'auto';
    foreach ($metas as $meta) {
        if (!empty($meta->charset)) { // html5
            $charset = $meta->charset;
        } else if (preg_match('@charset=(.+)@', $meta->content, $match)) {
            $charset = $match[1];
        }
    }
    if (!in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()))) {
        $charset = 'auto';
    }
    return mb_convert_encoding($title, 'UTF-8', $charset);
}

-1

Tôi gặp vấn đề tương tự với phpQuery ( ISO-8859-1 thay vì UTF-8 ) và bản hack này đã giúp tôi:

$html = '<?xml version="1.0" encoding="UTF-8" ?>' . $html;

mb_internal_encoding('UTF-8'), phpQuery::newDocumentHTML($html, 'utf-8'), mbstring.internal_encodingVà thao tác khác không có bất kỳ tác dụng.


-1

Hãy thử mà không có 'tự động'

Đó là:

mb_detect_encoding($text)

thay vì:

mb_detect_encoding($text, 'auto')

Thông tin chi tiết có thể được tìm thấy ở đây: mb_detect_encoding

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.