Php无法找到分割utf-8字符串的方法

时间:2011-12-03 17:39:11

标签: php utf-8 multibyte mbstring

我刚刚开始涉足php,我担心我需要一些帮助来弄清楚如何操作utf-8字符串。

我正在使用ubuntu 11.10 x86,php版本5.3.6-13ubuntu3.2。我有一个utf-8编码文件(vim :set encoding确认了这个),然后我继续使用

阅读它
$file = fopen("file.txt", "r");
while(!feof($file)){
    $line = fgets($file);
    //...
}
fclose($file);
  • 使用mb_detect_encoding($line)报告UTF-8
  • 如果我echo $line我可以在浏览器中正确看到该行(没有错位的字符)
    • 所以我猜浏览器和apache的一切都很好。虽然我确实搜索了AddDefaultCharset的apache配置,并尝试为字符编码添加http元标记(以防万一)

当我尝试使用$arr = mb_split(';',$line)拆分字符串时,结果数组的字段包含错误的utf-8字符(mb_detect_encoding($arr[0])报告utf-8)。

所以echo $arr[0]会产生类似这样的结果:ΑΘΗÎÎ

我尝试过设置mb_detect_order('utf-8')mb_internal_encoding('utf-8'),但没有任何改变。我还尝试使用this w3 perl regex手动检测utf-8,因为我在某处读到mb_detect_encoding有时会失败(神话?),但结果也一样。

所以我的问题是如何正确分割字符串?以错误的方式走下mb_路径?我错过了什么?

感谢您的帮助!

更新:我正在添加示例字符串和base64等价物(感谢@chris'的建议)

1. original string: "ΑΘΗΝΑ;ΑΙΓΑΛΕΩ;12242;37.99452;23.6889"
2. base64 encoded: "zpHOmM6Xzp3OkTvOkc6ZzpPOkc6bzpXOqTsxMjI0MjszNy45OTQ1MjsyMy42ODg5"
3. first part (the equivalent of "ΑΘΗΝΑ") base64 encoded before splitting: "zpHOmM6Xzp3OkQ=="
4. first part ($arr[0] after splitting): "ΑΘΗÎΑ"
5. first part after splitting base64 encoded: "77u/zpHOmM6Xzp3OkQ=="

好的,所以在这样做之后,似乎在3.和5之间存在77u/差异,according to this是一个utf-8 BOM标记。那我怎么能避免呢?

更新2 :我今天醒来时精神焕发,记住了你的提示我再试一次。似乎$line=fgets($file)正确读取了第一行(没有损坏的字符),并且对于每个后续行都失败了。那么我base64_encoded第一行和第二行,77u/ bom出现在第一行的base64'd字符串。然后我在vim中打开了违规文件,并输入了:set nobomb :w来保存文件,而不是bom。再次启动php显示第一行现在也被破坏了。基于@hakre的remove_utf8_bom我添加了它的补充功能

function add_utf8_bom($str){
    $bom= "\xEF\xBB\xBF";
    return substr($str,0,3)===$bom?$str:$bom.$str;
}

voila 现在每行都正确读取。

我不太喜欢这个解决方案,因为它看起来非常非常hackish(我无法相信整个框架/语言没有提供处理nobombed字符串的方法)。所以你知道另一种方法吗?否则我将继续上述。

感谢@ chris,@ hakre和@jacob的时间!

更新3(解决方案):事实证明它只是一个浏览器:仅添加header('Content-type: text/html; charset=UTF-8')<meta http-equiv="Content-type" value="text/html; charset=UTF-8" />等元标记是不够的。它也必须正确地包含在<html><body>部分内,否则浏览器将无法正确理解编码。感谢@jake的建议。

故事的故事:我首先应该在尝试编写浏览器之前了解更多有关html的信息。感谢您的帮助和耐心。

4 个答案:

答案 0 :(得分:4)

UTF-8具有与ASCII兼容的非常好的功能。我的意思是:

  • 编码为UTF-8时,ASCII字符保持不变
  • 其他字符不会被编码为ASCII字符

这意味着当您尝试使用分号字符;(ASCII字符)分割UTF-8字符串时,您可以使用标准的单字节字符串函数。

在您的示例中,您可以使用explode(';',$utf8encodedText),一切都应该按预期工作。

PS:由于UTF-8编码为prefix-free,因此您可以将explode()与任何UTF-8编码分隔符一起使用。

PPS:您似乎尝试解析CSV文件。看看fgetcsv()功能。只要您使用ASCII字符作为分隔符,引号等,它就可以完美地用于UTF-8编码的字符串。

答案 1 :(得分:1)

mb_splitDocs函数应该没问题,但您应该使用mb_regex_encodingDocs来定义它正在使用的字符集:

mb_regex_encoding('UTF-8');

关于mb_detect_encodingDocs:它可能会失败,但这只是因为你永远无法检测到编码。你要么知道它,要么你可以试试,但就是这样。编码检测主要是一种赌博游戏,但您可以在该功能中使用strict参数并指定您正在寻找的编码。

如何删除BOM掩码:

您可以过滤字符串输入并使用小辅助函数删除UTF-8 bom:

/**
 * remove UTF-8 BOM if string has it at the beginning
 *
 * @param string $str
 * @return string
 */
function remove_utf8_bom($str)
{
   if ($bytes = substr($str, 0, 3) && $bytes === "\xEF\xBB\xBF") 
   {
       $str = substr($str, 3);
   }
   return $str;
}

用法:

$line = remove_utf8_bom($line);

可能有更好的方法,但这应该有用。

答案 2 :(得分:1)

编辑,我刚看完你的帖子。您建议输出为false,因为您建议mb_split()引入BOM。

header('content-type: text/plain;charset=utf-8');
$s = "zpHOmM6Xzp3OkTvOkc6ZzpPOkc6bzpXOqTsxMjI0MjszNy45OTQ1MjsyMy42ODg5";
$str = base64_decode($s);

$peices = mb_split(';', $str);

var_dump(substr($str, 0, 10) === $peices[0]);
var_dump($peices);
是吗?它按预期的方式工作(bool true,数组中的字符串是正确的)

答案 3 :(得分:1)

在php中编写调试/测试脚本时,请确保输出或多或少有效的HTML页面。

我喜欢使用类似于以下内容的PHP文件:

<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>Test page for project XY</title>
  </head>
  <body>
     <h1>Test Page</h1>
     <pre><?php
        echo print_r($_GET,1);
     ?></pre>
  </body>
</html>

如果您不包含任何HTML标记,浏览器可能会将该文件解释为文本文件,并且可能会发生各种奇怪的事情。在您的情况下,我假设浏览器将该文件解释为Latin1编码的文本文件。我认为它适用于BOM,因为每当BOM存在时,浏览器都会将该文件识别为UTF-8文件。