如果您尝试使用 mb_detect_encoding 检测字符串是否为有效的 UTF-8,请使用严格模式,否则它几乎毫无用处。
<?php
$str = 'áéóú'; // ISO-8859-1
mb_detect_encoding($str, 'UTF-8'); // 'UTF-8'
mb_detect_encoding($str, 'UTF-8', true); // false
?>
(PHP 4 >= 4.0.6, PHP 5, PHP 7, PHP 8)
mb_detect_encoding — 检测字符编码
$string
, array|string|null $encodings
= null
, bool $strict
= false
): string|false从候选字符编码的有序列表中检测 string string
最可能的字符编码。
字符编码的自动检测从来不可能完全可靠;如果没有一些附加信息,它类似于在没有密钥的情况下解码加密字符串。始终最好使用与数据一起存储或传输的字符编码指示,例如“Content-Type” HTTP 标头。
此函数对多字节编码最为有用,其中并非所有字节序列都构成有效的字符串。如果输入字符串包含这样的序列,则该编码将被拒绝,并检查下一个编码。
string
正在检查的 string。
encodings
要尝试的字符编码列表,按顺序排列。该列表可以指定为字符串数组,也可以指定为逗号分隔的单个字符串。
如果省略 encodings
或为 null
,则将使用当前的 detect_order(使用 mbstring.detect_order 配置选项或 mb_detect_order() 函数设置)。
strict
控制当 string
在列出的 encodings
中均无效时的行为。如果 strict
设置为 false
,则返回最接近匹配的编码;如果 strict
设置为 true
,则返回 false
。
strict
的默认值可以使用 mbstring.strict_detection 配置选项设置。
检测到的字符编码,如果字符串在任何列出的编码中均无效,则返回 false
。
版本 | 描述 |
---|---|
8.2.0 |
mb_detect_encoding() 将不再返回以下非文本编码:"Base64" 、"QPrint" 、"UUencode" 、"HTML entities" 、"7 bit" 和 "8 bit" 。 |
示例 #1 mb_detect_encoding() 示例
<?php
// 使用当前 detect_order 检测字符编码
echo mb_detect_encoding($str);
// “auto” 根据 mbstring.language 展开
echo mb_detect_encoding($str, "auto");
// 用逗号分隔的列表指定 “encodings” 参数
echo mb_detect_encoding($str, "JIS, eucjp-win, sjis-win");
// 使用数组指定 “encodings” 参数
$encodings = [
"ASCII",
"JIS",
"EUC-JP"
];
echo mb_detect_encoding($str, $encodings);
?>
示例 #2 strict
参数的影响
<?php
// 'áéóú' 使用 ISO-8859-1 编码
$str = "\xE1\xE9\xF3\xFA";
// 该字符串在 ASCII 或 UTF-8 中无效,但 UTF-8 被认为是更接近的匹配
var_dump(mb_detect_encoding($str, ['ASCII', 'UTF-8'], false));
var_dump(mb_detect_encoding($str, ['ASCII', 'UTF-8'], true));
// 如果找到有效的编码,则 strict 参数不会更改结果
var_dump(mb_detect_encoding($str, ['ASCII', 'UTF-8', 'ISO-8859-1'], false));
var_dump(mb_detect_encoding($str, ['ASCII', 'UTF-8', 'ISO-8859-1'], true));
?>
以上示例将输出
string(5) "UTF-8" bool(false) string(10) "ISO-8859-1" string(10) "ISO-8859-1"
在某些情况下,相同的字节序列可能在多种字符编码中构成有效的字符串,并且无法知道哪个解释是预期的。例如,在许多其他情况下,字节序列“\xC4\xA2”可能是
示例 #3 多种编码匹配时顺序的影响
<?php
$str = "\xC4\xA2";
// 字符串在三种编码中均有效,因此将返回首先列出的编码
var_dump(mb_detect_encoding($str, ['UTF-8', 'ISO-8859-1', 'ISO-8859-5']));
var_dump(mb_detect_encoding($str, ['ISO-8859-1', 'ISO-8859-5', 'UTF-8']));
var_dump(mb_detect_encoding($str, ['ISO-8859-5', 'UTF-8', 'ISO-8859-1']));
?>
以上示例将输出
string(5) "UTF-8" string(10) "ISO-8859-1" string(10) "ISO-8859-5"
如果您尝试使用 mb_detect_encoding 检测字符串是否为有效的 UTF-8,请使用严格模式,否则它几乎毫无用处。
<?php
$str = 'áéóú'; // ISO-8859-1
mb_detect_encoding($str, 'UTF-8'); // 'UTF-8'
mb_detect_encoding($str, 'UTF-8', true); // false
?>
PHP 8.1 的文档不再正确,mb_detect_encoding 不再支持编码顺序。文档中给出的示例输出对于 PHP 8.1 也已经不再正确。这里有一些解释 https://github.com/php/php-src/issues/8279
我理解这些函数之前的歧义,但在我看来,8.1 应该弃用 mb_detect_encoding 和 mb_detect_order,并提出不同的函数。它现在尝试找到将使用最少空间的编码,而不管顺序如何,我不确定谁需要这个。
下面是一个示例函数,它将执行 mb_detect_encoding 在 8.1 更改之前所执行的操作。
<?php
function mb_detect_enconding_in_order(string $string, array $encodings): string|false
{
foreach($encodings as $enc) {
if (mb_check_encoding($string, $enc)) {
return $enc;
}
}
return false;
}
?>
自 8.1.7 版本以来,重大未记录的重大更改
https://3v4l.org/BLjZ3
确保将 mb_detect_encoding 替换为对 mb_check_encoding 的循环调用
如果您需要区分 UTF-8 和 ISO-8859-1 编码,请在您的 encoding_list 中首先列出 UTF-8
mb_detect_encoding($string, 'UTF-8, ISO-8859-1');
如果您首先列出 ISO-8859-1,mb_detect_encoding() 将始终返回 ISO-8859-1。
基于下面的使用 preg_match() 的代码片段,我需要更快、更不具体的解决方案。该函数有效且很棒,但它会扫描整个字符串并检查它是否符合 UTF-8。我想要一些纯粹用于检查字符串是否包含 UTF-8 字符的东西,以便我可以将字符编码从 iso-8859-1 切换到 utf-8。
我修改了模式,只查找 UTF-8 范围内的非 ASCII 多字节序列,并在找到至少一个多字节字符串后停止。这快得多。
<?php
function detectUTF8($string)
{
return preg_match('%(?:
[\xC2-\xDF][\x80-\xBF] # 非超长 2 字节
|\xE0[\xA0-\xBF][\x80-\xBF] # 排除超长字符
|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # 直接 3 字节
|\xED[\x80-\x9F][\x80-\xBF] # 排除代理对
|\xF0[\x90-\xBF][\x80-\xBF]{2} # 平面 1-3
|[\xF1-\xF3][\x80-\xBF]{3} # 平面 4-15
|\xF4[\x80-\x8F][\x80-\xBF]{2} # 平面 16
)+%xs', $string);
}
?>
一种通过其 BOM 检测文件 UTF-8/16/32 的简单方法(不适用于不带 BOM 的字符串或文件)
<?php
// Unicode BOM 是 U+FEFF,但编码后,它将如下所示。
define ('UTF32_BIG_ENDIAN_BOM' , chr(0x00) . chr(0x00) . chr(0xFE) . chr(0xFF));
define ('UTF32_LITTLE_ENDIAN_BOM', chr(0xFF) . chr(0xFE) . chr(0x00) . chr(0x00));
define ('UTF16_BIG_ENDIAN_BOM' , chr(0xFE) . chr(0xFF));
define ('UTF16_LITTLE_ENDIAN_BOM', chr(0xFF) . chr(0xFE));
define ('UTF8_BOM' , chr(0xEF) . chr(0xBB) . chr(0xBF));
function detect_utf_encoding($filename) {
$text = file_get_contents($filename);
$first2 = substr($text, 0, 2);
$first3 = substr($text, 0, 3);
$first4 = substr($text, 0, 3);
if ($first3 == UTF8_BOM) return 'UTF-8';
elseif ($first4 == UTF32_BIG_ENDIAN_BOM) return 'UTF-32BE';
elseif ($first4 == UTF32_LITTLE_ENDIAN_BOM) return 'UTF-32LE';
elseif ($first2 == UTF16_BIG_ENDIAN_BOM) return 'UTF-16BE';
elseif ($first2 == UTF16_LITTLE_ENDIAN_BOM) return 'UTF-16LE';
}
?>
我使用 Chris 的函数“detectUTF8”来检测从 utf8 到 8859-1 转换的需要,这很好用。我在以下 iconv 转换中遇到问题。
问题在于,转换为 8859-1(使用 //TRANSLIT)会将欧元符号替换为 EUR,尽管通常的做法是在 8859-1 字符集中使用 \x80 作为欧元符号。
我无法使用 8859-15,因为它破坏了一些其他字符,所以我添加了 2 个 str_replace。
if(detectUTF8($str)){
$str=str_replace("\xE2\x82\xAC","€",$str);
$str=iconv("UTF-8","ISO-8859-1//TRANSLIT",$str);
$str=str_replace("€","\x80",$str);
}
如果需要 html 输出,则最后一行不是必需的(甚至是不需要的)。
只是一个说明:您可以简单地使用“u”修饰符来测试字符串的 UTF-8 有效性,而不是使用 W3C 常用的(相当复杂的)正则表达式 (http://www.w3.org/International/questions/qa-forms-utf-8.en.php)。
<?php
if (preg_match("//u", $string)) {
// $string 是有效的 UTF-8
}
当 `mb_detect_encoding` 函数不可用时,此函数用于检测 UTF-8 编码,可能会有用。
<?php
function is_utf8($str) {
$c=0; $b=0;
$bits=0;
$len=strlen($str);
for($i=0; $i<$len; $i++){
$c=ord($str[$i]);
if($c > 128){
if(($c >= 254)) return false;
elseif($c >= 252) $bits=6;
elseif($c >= 248) $bits=5;
elseif($c >= 240) $bits=4;
elseif($c >= 224) $bits=3;
elseif($c >= 192) $bits=2;
else return false;
if(($i+$bits) > $len) return false;
while($bits > 1){
$i++;
$b=ord($str[$i]);
if($b < 128 || $b > 191) return false;
$bits--;
}
}
}
return true;
}
?>
使用W3C创建的正则表达式进行更简单的UTF-8检查
<?php
// 返回true表示$string是有效的UTF-8,否则返回false。
function is_utf8($string) {
// 来自 http://w3.org/International/questions/qa-forms-utf-8.html
return preg_match('%^(?:
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # 非冗余2字节
| \xE0[\xA0-\xBF][\x80-\xBF] # 排除冗余
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # 直接3字节
| \xED[\x80-\x9F][\x80-\xBF] # 排除代理
| \xF0[\x90-\xBF][\x80-\xBF]{2} # 平面1-3
| [\xF1-\xF3][\x80-\xBF]{3} # 平面4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # 平面16
)*$%xs', $string);
} // function is_utf8
?>
用于检测UTF-8,您可以使用
if (preg_match('!!u', $str)) { echo 'utf-8'; }
- Norihiori
有时 `mb_detect_string` 函数并非您所需。例如,当使用pdflib时,您需要验证UTF-8的正确性。`mb_detect_encoding` 会将某些ISO-8859-1编码的文本报告为UTF-8。
要验证utf-8,请使用以下方法
//
// 基于维基百科条目开发的utf8编码验证
// http://en.wikipedia.org/wiki/UTF-8
//
// 基于简单的状态机的递归下降解析器实现
// 版权2005 Maarten Meijer
//
// 这需要一个C语言实现才能包含在PHP核心之中
//
function valid_1byte($char) {
if(!is_int($char)) return false;
return ($char & 0x80) == 0x00;
}
function valid_2byte($char) {
if(!is_int($char)) return false;
return ($char & 0xE0) == 0xC0;
}
function valid_3byte($char) {
if(!is_int($char)) return false;
return ($char & 0xF0) == 0xE0;
}
function valid_4byte($char) {
if(!is_int($char)) return false;
return ($char & 0xF8) == 0xF0;
}
function valid_nextbyte($char) {
if(!is_int($char)) return false;
return ($char & 0xC0) == 0x80;
}
function valid_utf8($string) {
$len = strlen($string);
$i = 0;
while( $i < $len ) {
$char = ord(substr($string, $i++, 1));
if(valid_1byte($char)) { // 继续
continue;
} else if(valid_2byte($char)) { // 检查1字节
if(!valid_nextbyte(ord(substr($string, $i++, 1))))
return false;
} else if(valid_3byte($char)) { // 检查2字节
if(!valid_nextbyte(ord(substr($string, $i++, 1))))
return false;
if(!valid_nextbyte(ord(substr($string, $i++, 1))))
return false;
} else if(valid_4byte($char)) { // 检查3字节
if(!valid_nextbyte(ord(substr($string, $i++, 1))))
return false;
if(!valid_nextbyte(ord(substr($string, $i++, 1))))
return false;
if(!valid_nextbyte(ord(substr($string, $i++, 1))))
return false;
} // 跳转到下一个字符
}
return true; // 完成
}
状态机的示意图请参见: http://www.xs4all.nl/~mjmeijer/unicode.png 和 http://www.xs4all.nl/~mjmeijer/unicode2.png
这对我的 `exec(...)` 调用很有帮助。当它返回 cp866 或 cp1251 时
try {
$line = iconv('CP866', 'CP1251', $line);
} catch(Exception $e) {
}
return iconv('CP1251', 'UTF-8', $line);
如果函数“mb_detect_encoding”不存在……
……尝试
<?php
// ----------------------------------------------------
if ( !function_exists('mb_detect_encoding') ) {
// ----------------------------------------------------------------
function mb_detect_encoding ($string, $enc=null, $ret=null) {
static $enclist = array(
'UTF-8', 'ASCII',
'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', 'ISO-8859-5',
'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9', 'ISO-8859-10',
'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
'Windows-1251', 'Windows-1252', 'Windows-1254',
);
$result = false;
foreach ($enclist as $item) {
$sample = iconv($item, $item, $string);
if (md5($sample) == md5($string)) {
if ($ret === NULL) { $result = $item; } else { $result = true; }
break;
}
}
return $result;
}
// ----------------------------------------------------------------
}
// ----------------------------------------------------
?>
mb_detect_encoding() 的示例/用法
<?php
// ------------------------------------------------------
function str_to_utf8 ($str) {
if (mb_detect_encoding($str, 'UTF-8', true) === false) {
$str = utf8_encode($str);
}
return $str;
}
// ------------------------------------------------------
?>
$txtstr = str_to_utf8($txtstr);
a) 如果函数 `mb_detect_encoding` 不可用
### mb_detect_encoding ... iconv ###
<?php
// -------------------------------------------
if(!function_exists('mb_detect_encoding')) {
function mb_detect_encoding($string, $enc=null) {
static $list = array('utf-8', 'iso-8859-1', 'windows-1251');
foreach ($list as $item) {
$sample = iconv($item, $item, $string);
if (md5($sample) == md5($string)) {
if ($enc == $item) { return true; } else { return $item; }
}
}
return null;
}
}
// -------------------------------------------
?>
b) 如果`mb_convert_encoding`函数不可用
### mb_convert_encoding ... iconv ###
<?php
// -------------------------------------------
if(!function_exists('mb_convert_encoding')) {
function mb_convert_encoding($string, $target_encoding, $source_encoding) {
$string = iconv($source_encoding, $target_encoding, $string);
return $string;
}
}
// -------------------------------------------
?>
注意:即使你需要区分UTF-8和ISO-8859-1,并且你使用以下检测顺序(正如chrigu建议的那样)
mb_detect_encoding('accentu?e' , 'UTF-8, ISO-8859-1')
返回ISO-8859-1,而
mb_detect_encoding('accentu?' , 'UTF-8, ISO-8859-1')
返回UTF-8
底线:结尾的'?'(以及可能其他的重音字符)会误导mb_detect_encoding
在我的环境(PHP 7.1.12)中,
“mb_detect_encoding()”不起作用
其中“mb_detect_order()”没有被正确设置。
为了在这种情况下启用“mb_detect_encoding()”,
只需在你的脚本文件中放置“mb_detect_order('...')”
在“mb_detect_encoding()”之前。
两者
“ini_set('mbstring.language', '...');”
和
“ini_set('mbstring.detect_order', '...');”
都不能用于此目的在脚本文件中工作
而将其设置在PHP.INI文件中可能会起作用。
关于mb_detect_encoding函数,链接https://php.net/manual/zh/function.mb-detect-encoding.php,例如:
mb_detect_encoding('áéóú', 'UTF-8', true); // false
但是现在的结果不是false,你能告诉我原因吗,谢谢!
<?php
$file = file_get_contents("somefile.txt");
$encodings = implode(',', mb_list_encodings());
echo mb_detect_encoding($file, $encodings, true);
?>
似乎有效