1

我有一个网站,它的 PHP 将所有其他文件收集在与当前脚本相同的目录中,并由此生成一个菜单(此菜单包括运行脚本的当前文件)。

当我从这个文件名列表生成这个菜单的 HTML 时,我检查每个文件名是否等于当前文件的名称(通过 __ FILE __)。如果是这样,我会应用一种样式来突出显示菜单中的该项目。

我的文件名带有法语口音,因为文件名也用于页面标题。这在 Chrome 和 Firefox 中运行良好,但 Safari 和 IOS 不起作用;法语口音在某个地方搞砸了这个过程,因为从我的文件名中删除法语口音可以解决问题。

这是我的代码:

从当前目录获取所有相关文件

if ($handle = opendir(getcwd())) {
    $albums = array();
    while (false !== ($entry = readdir($handle))) {
        if(is_numeric(substr($entry, 0, 4))) array_push($albums, $entry);
    }
    closedir($handle);
}

这是我的字符串比较,简化了,它们的 var_dumps:(未添加文件名清理,假设两个 var 都给出了名称文件名结构)

for($i=0; $i < count($albums); $i++){
    echo var_dump($albums[$i]); echo var_dump(basename(__FILE__));
}

及其产生的回声:

string(26) "2010_Kalymnos,_Grèce.php" 
string(25) "2010_Kalymnos,_Grèce.php" 

当我尝试强制使用 UTF_8 或 ASCII 来查看他们如何处理法语口音时,他们会以不同的方式转换口音,但我不知道是什么原因造成的。是我用来获取文件的方法吗(__ FILE __, readdir())?

我的 HTML 文件是 utf-8,以防这很重要。将我的 PHP 专门设置为 UTF-8 也不能解决问题。

编辑

<?php echo bin2hex($albums[$i]); echo '<br/>'.bin2hex($originFilename);?>

结果是:

323031305f4b616c796d6e6f732c5f477265cc8063652e706870
323031305f4b616c796d6e6f732c5f4772c3a863652e706870

两者中,第一个十六进制字符串是正确的。

4

1 回答 1

1

给定十六进制编码的输出,我们可以看到两个字符串的不同之处。第一个读取65cc80第二个读取的位置c3a8。这表明您是非规范化 Unicode 字符串的受害者。

第一个序列对应于两个 Unicode 字符U+0065LATIN SMALL LETTER E)和U+0300COMBINING GRAVE ACCENT)。如您所见,连接它们的 UTF-8 编码形式会得到十六进制编码的字节序列0x65cc80

第二个序列对应于单个 Unicode 字符U+00E8LATIN SMALL LETTER E WITH GRAVE),编码为0xc3a8.

这里发生的情况是,您有两个字节序列,它们位不相同,但在 Unicode 规则下在逻辑上是等效的。当您想要比较字符串时,您需要一个编码和规范化感知比较函数,或者您需要预先对字符串进行规范化(然后您可以使用一个哑比较函数,例如字符串相等)。

不幸的是,我不知道在 PHP 中进行逻辑等价比较的任何方法,因此解决方案是安装 intl 扩展并使用Normalizer该类将两个字符串转换为规范化表单 C 和Normalizer::normalize.

于 2013-09-19T20:35:15.993 回答