从导入的 .csv 文件中删除 BOM ()

17

我想从我的导入文件中删除BOM,但似乎无法实现。

我尝试了preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $file);和str_replace。

我希望有人能看出我做错了什么。

$filepath = get_bloginfo('template_directory')."/testing.csv";
            setlocale(LC_ALL, 'nl_NL');
            ini_set('auto_detect_line_endings',TRUE);
            $file = fopen($filepath, "r") or die("Error opening file");
            $i = 0;
            while(($line = fgetcsv($file, 1000, ";")) !== FALSE) {
                if($i == 0) {
                    $c = 0;
                    foreach($line as $col) {
                        $cols[$c] = utf8_encode($col);
                        $c++;
                    }
                } else if($i > 0) {
                    $c = 0;
                    foreach($line as $col) {
                        $data[$i][$cols[$c]] = utf8_encode($col);
                        $c++;
                    }
                }
                $i++;
            }

-----------
已解决版本:

setlocale(LC_ALL, 'nl_NL');
ini_set('auto_detect_line_endings',TRUE);
require_once(ABSPATH.'wp-admin/includes/file.php' );

$path = get_home_path();        
$filepath = $path .'wp-content/themes/pon/testing.csv';
$content = file_get_contents($filepath); 
file_put_contents($filepath, str_replace("\xEF\xBB\xBF",'', $content));

// FILE_PUT_CONTENTS AUTOMATICCALY CLOSES THE FILE
$file = fopen($filepath, "r") or die("Error opening file"); 

$i = 0;
while(($line = fgetcsv($file, 1000, ";")) !== FALSE) {
    if($i == 0) {
        $c = 0;
        foreach($line as $col) {
            $cols[$c] = $col;
            $c++;
        }
    } else if($i > 0) {
        $c = 0;
        foreach($line as $col) {
            $data[$i][$cols[$c]] = $col;
            $c++;
        }
    }
    $i++;
}

我发现它会移除文件头的BOM并通过覆盖新数据来调整文件。问题是我的其余脚本不再起作用,而我无法确定原因。这是一个新的 .csv 文件。


你第一个foreach循环中的$cols[$c]是无意义的。$cols是你正在处理的任何行/字段的副本。你需要使用foreach($lines as $key => $col) { $lines[$key] = utf8_encode($col); } - Marc B
2
PHP文档中fgetcsv的注释有一个很好的答案,https://www.php.net/manual/en/function.fgetcsv.php#122696 - 打开文件,读取并移动文件指针,并检查前3个字节是否等于BOM字符串,如果不是,则倒回并继续使用fgetcv。 - jave.web
这个编程问题已经在这里得到解决:https://dev59.com/R2035IYBdhLWcg3wef9yfixedstring = decodeURIComponent(escape(utfstring)); - olivia
7个回答

27

试一下这个:

function removeBomUtf8($s){
  if(substr($s,0,3)==chr(hexdec('EF')).chr(hexdec('BB')).chr(hexdec('BF'))){
       return substr($s,3);
   }else{
       return $s;
   }
}

它给了我这个:警告:substr()期望参数1为字符串,但提供了资源 - Interactive
你将传递什么参数到这个函数中?应该像这样:$file = 'something.csv';$content = file_get_contents($file);var_dump(removeBomUtf8($content)); 然后开始处理这个文件。 - Tomasz
1
在这行代码中:$content = file_get_contents($file);,将 $file 改为 $filepath - Tomasz
我稍微修改了你的想法(不知道是否是最好的,但它有效)。我发现 file_put_contents 关闭了文件,所以我只需要重新打开它。谢谢你的帮助。 - Interactive
2
删除UTF16小端BOM (substr($s, 0, 2) == chr(0xFF).chr(0xFE)) - Nolwennig
显示剩余2条评论

7

正确的做法是在文件中跳过BOM(如果存在)(https://www.php.net/manual/en/function.fgetcsv.php#122696):

ini_set('auto_detect_line_endings',TRUE);
$file = fopen($filepath, "r") or die("Error opening file");
if (fgets($file, 4) !== "\xef\xbb\xbf") //Skip BOM if present
        rewind($file); //Or rewind pointer to start of file

$i = 0;
while(($line = fgetcsv($file, 1000, ";")) !== FALSE) {
    ...
}

1
这是正确的,但它不会删除BOM,这就是问题所在。 - mariovials
3
auto_detect_line_endings 在 PHP 8.1 中已被弃用,将在 PHP 9.0 中移除。https://php.watch/versions/8.1/auto_detect_line_endings-ini-deprecated - Buttle Butkus

6

难道BOM不是为了让你明白如何将输入重新编码为脚本/应用程序/数据库所需的格式吗?仅仅删除是没有任何帮助的。

以下是我如何强制一个字符串(从file_get_contents()读取文件)以UTF-8编码,并且同时摆脱BOM:

switch (true) { 
    case (substr($string,0,3) == "\xef\xbb\xbf") :
        $string = substr($string, 3);
        break;
    case (substr($string,0,2) == "\xfe\xff") :                            
        $string = mb_convert_encoding(substr($string, 2), "UTF-8", "UTF-16BE");
        break;
    case (substr($string,0,2) == "\xff\xfe") :                            
        $string = mb_convert_encoding(substr($string, 2), "UTF-8", "UTF-16LE");
        break;
    case (substr($string,0,4) == "\x00\x00\xfe\xff") :
        $string = mb_convert_encoding(substr($string, 4), "UTF-8", "UTF-32BE");
        break;
    case (substr($string,0,4) == "\xff\xfe\x00\x00") :
        $string = mb_convert_encoding(substr($string, 4), "UTF-8", "UTF-32LE");
        break;
    default:
        $string = iconv(mb_detect_encoding($string, mb_detect_order(), true), "UTF-8", $string);
};

1
我喜欢这个,除了UTF-32LE永远不会被检测到,因为UTF-16LE会先触发它。最长的比较应该放在顶部。 - bwaindwain

4
如果字符编码函数对您没有用(在某些情况下是这样),并且您确实知道您的文件总是有BOM,那么您可以简单地使用fseek()跳过前3个字节,即BOM的长度。
$fp = fopen("testing.csv", "r");
fseek($fp, 3);

不要使用explode()来拆分CSV行和列,因为如果您的列包含您拆分的字符,您将得到一个错误的结果。请改用以下方法:

while (!feof($fp)) {
    $arrayLine = fgetcsv($fp, 0, ";", '"');
    ...
}

5
如果无法确定是否存在 BOM 标记,最好检查并回绕到开头(rewind),而不是使用 fseek。代码示例:if (!fread($handle, 3)==chr(0xEF).chr(0xBB).chr(0xBF)) { rewind($handle); }注意,翻译保持了原文的意思和结构,同时尽可能地通俗易懂。 - DanielW

1

使用file_get_contents读取数据,然后使用mb_convert_encoding转换为UTF-8编码。

更新

$filepath = get_bloginfo('template_directory')."/testing.csv";
$fileContent = file_get_contents($filepath);
$fileContent = mb_convert_encoding($fileContent, "UTF-8");
$lines = explode("\n", $fileContent);
foreach($lines as $line) {
    $conls = explode(";", $line);
    // etc...
}

@Interactive file_get_contents 会读取整个文件。使用 "\n" 或 "\r\n" 对其进行 explode,它将返回一个数组。然后遍历这个数组。 - MrRP
如果我运行这个程序,它会给我一个数组,其中“titlefields”在第一个数组中,每个后续的数组都包含每个人的信息。这很好,但我不知道如何将其用于我正在做的事情。所以我想我得熬夜了。 - Interactive
我稍微修改了你的想法(不知道这是不是最好的方法,但它是有效的)。我发现file_put_contents函数会关闭文件,所以我只需要重新打开它即可。感谢你的帮助。 - Interactive

1

0

@Tomas'z的回答为主要灵感,并参考了@Nolwennig的评论

// Strip byte order marks from a string
function strip_bom($string, $type = 'utf8') {
    $length = 0;

    switch($type) {
        case 'utf8':
            $length = substr($string, 0, 3) === chr(0xEF) . chr(0xBB) . chr(0xBF) ? 3 : 0;
        break;

        case 'utf16_little_endian':
            $length = substr($string, 0, 2) === chr(0xFF) . chr(0xFE) ? 2 : 0;
        break;
    }

    return $length ? substr($string, $length) : $string;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接