【问题标题】:Php cannot find way to split utf-8 stringsphp找不到分割utf-8字符串的方法
【发布时间】:2011-12-03 17:39:11
【问题描述】:

我刚开始涉足 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 一切正常。虽然我确实在我的 apache 配置中搜索了 AddDefaultCharset 并尝试为字符编码添加 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;
}

并且现在每一行都被正确读取了。

我不太喜欢这个解决方案,因为它看起来非常老套(我不敢相信整个框架/语言都没有提供一种处理无炸弹字符串的方法)。那么你知道另一种方法吗?否则我会继续上面的。

感谢@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 的知识。感谢大家的帮助和耐心。

【问题讨论】:

  • 我建议您发布示例字符串(拆分前后)供人们检查。为了保护它们的二进制安全,base64_encode() 它们,否则细节将不会通过网络浏览器和 stackoverflow 等保存......
  • @chris +1 看来使用 base64 你可能会有所收获
  • 这里有些奇怪。我总是在 PHP 中使用没有 BOM 的 UTF8 字符串,它可以正常工作。你如何输出变量?你只是做echo $line吗?您是否输出整个网页,即带有文档类型、标题等?还是你在命令行上使用 PHP?
  • @jakob 我在使用 apache2 的独立网站(即没有加载 wordpress 环境等)中使用 test.php 文件,然后我使用 Firefox 浏览该文件。我只是像你说的那样 echo $line ,然后我逐渐尝试使用元标记和 header() 以及诸如此类的东西来声明 utf-8 编码,希望它是这样的,但什么都没有。我不争辩说问题出在我所做的某处,我只是不知道它是什么!
  • @bottlenecked: 我不知道你是否已经这样做了,但是尝试在你的 test.php 文件中输出有效的 HTML,即在你写 echo $line 之前,写一些类似 echo '<!DOCTYPE html><html><head><meta charset=utf-8><title>Test Page</title></head><body>'; 的东西.

标签: php utf-8 multibyte mbstring


【解决方案1】:

UTF-8 有一个非常好的特性,它是 ASCII 兼容的。我的意思是:

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

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

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

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

PPS:您似乎在尝试解析 CSV 文件。看看fgetcsv() 函数。只要您使用 ASCII 字符作为分隔符、引号等,它应该可以完美地处理 UTF-8 编码的字符串。

【讨论】:

  • 确实,explode 是我一开始使用的,当我无法让它工作时,它后来引导我阅读有关 mbstrings 的内容
  • 那你的问题可能是html页面的输出编码不是UTF-8。检查您是否在页眉某处有<meta charset=utf-8>
  • 我试过了(在过长的问题陈述中也提到过),但又没用。我还用新的发现再次更新了这个问题。
【解决方案2】:

当您在 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 文件。

【讨论】:

  • 猜猜就是这样!我现在更聪明了:P
【解决方案3】:

编辑,我刚刚仔细阅读了您的帖子。您建议这应该输出 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,并且数组中的字符串是正确的)

【讨论】:

  • 是的,它就像你说的那样工作。从文件本身读取同一行时似乎出现了问题
  • 你确定你在发布 base64_encoded 字符串时没有犯错吗?因为 orig base64 字符串没有 BOM,我假设你应该是直接从 fgets 返回的值,第一行也是。
  • 是的。搞砸了。这是“从编辑器手动复制行,然后粘贴到 php 文件中作为 base64_encode 的参数”之类的东西,因为我目前不了解这一切的全部含义。对不起,红鲱鱼:(
【解决方案4】:

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

可能有更好的方法来做到这一点,但这应该可行。

【讨论】:

  • 我对你的字符串没有任何问题,实际上即使是简单的爆炸也应该适用于 UTF-8 编码的字符串。请参阅codepad.viper-7.com/eODqA5 - 看起来您将结果视为 ISO-8859-*。
  • 使用 add_utf8_bom,explode 对每一行都按预期工作。如果没有出现更好(即不那么骇人听闻)的解决方案,我会接受这个答案
  • 不那么麻烦的方法是保存file.txt w/o BOM。这就是针对此类问题首先提出的建议,请参阅unicode.org/faq/utf_bom.html#BOM。如果文件已经包含 BOM,还要了解在 vim 中需要做什么来删除 BOM。 mb_split 在我看来工作正常,因为它应该保留 BOM,因为它也是一个有效的 unicode 代码点:fileformat.info/info/unicode/char/feff/index.htm - 所以你最好给你的应用程序直接正确编码的字符串,或者你在解析之前修复它,或者你继续使用 hack ;)
猜你喜欢
  • 2016-05-31
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 2012-01-20
  • 2021-10-11
  • 2016-08-29
  • 1970-01-01
相关资源
最近更新 更多