在PHP中确定.csv分隔符


Determine .csv delimiter in PHP

注意:我首先会说,我知道我可能错过了一些非常明显的东西。我陷入了一种编码的困境,我看不到简单的解决方案。

问题:我用PHP编写了一个脚本来解析.csv文件,选择包含电子邮件地址的列,并将它们放入数据库。现在,我发现用户试图上传具有.csv文件类型的文件,但实际上并不是逗号分隔的。我正试图编写一个函数来正确确定分隔符(制表符、新行、空格等),但遇到了一些问题。我想我想得到所有这些地址的数组,这样键的数量就会增加分隔符的可信度。

代码:

$filename = "../some/path/test.csv";   
if (($handle = fopen($fileName, "r")) !== FALSE) {
    $delimiters = array(',', ' ', "'t", "'n");
    $delimNum = 0;
    foreach ($delimiters as $delimiter) {
      $row = 0;
      while (($data = fgetcsv($handle, 1000, $delimiter)) !== FALSE) {
        $data = (string)$data[0];
        $delimiterList[$delimNum] = explode($delimiter, $data);
        $row++;
    }
    $delimNum++;
}
die(print_r($delimiterList));
}

结果:

Array
(
[0] => Array
    (
        [0] => email
peter.parker@example.com
atticus.finch@example.com
steve.rogers@example.com
phileas.fogg@example.com
s.winston@example.com
paul.revere@example.com
fscott.fitzgerald@example.com
jules.verne@example.com
martin.luther@example.com
ulysses.grant@example.com
tony.stark@example.com
    )
)

正如我所说,我知道这可能是错误的做法,所以我感谢你能提供的任何见解!

用可用性而不是代码来解决这个问题。让用户选择分隔符。

然而,由于他们可能不知道什么是制表符分隔的CSV等,所以只需向他们显示预览即可。他们可以从选项中进行选择,直到输出看起来正确且表格化。

然后根据选择的格式对其进行解析。

我将展示一个算法,它可能是一个很好的解决方案,不要认为这个问题很容易,这就像猜测,所以这个问题不会有完美的解决方案。

相反,应该尝试使用统计学或其他一些启发式方法来近似99%的好解决方案。我是一名计算机科学家,也是一名开发人员,但这是机器学习或数据科学家会给出的近似值。

这是:

  1. 从文件的所有行中随机选取若干行,例如10行
  2. 统计每个候选分隔符的出现次数
  3. 用这个数字计算每个分隔符的平均值和方差
  4. 归一化数字,这意味着使用自定义线性函数给出0到1之间的数字
  5. 将每个分隔符的两个值加权并求和,这将为每个分隔符提供一个分数,您可以将其用作决策

看起来很复杂,但这是一个非常好且不难的算法。下面是一个计算示例:

分隔符计数(直方图)

|         | ; | , | 't  |
|---------|---|---|-----|
| LINE 1  | 3 | 1 |  13 |
| LINE 2  | 2 | 1 |   0 |
| LINE 3  | 3 | 1 |   0 |
| LINE 4  | 3 | 1 | 124 |
| LINE 5  | 2 | 1 |   2 |
| LINE 6  | 2 | 1 |   2 |
| LINE 7  | 3 | 1 |  12 |
| LINE 8  | 3 | 1 |   0 |
| LINE 9  | 3 | 1 |   0 |
| LINE 10 | 3 | 1 |   0 |

计算和最终得分

|            |  ;   |  ,   |  't  |  | WEIGHTS |  ;   |  ,   | 't |
|------------|------|------|------|--|---------|------|------|----|
| AVERAGE    |  2,7 |    1 | 15,3 |  |         |      |      |    |
| NORMALIZED | 0,17 | 0,06 |    1 |  | 1       | 0,17 | 0,06 |  1 |
| VARIANCE   | 0,21 |    0 | 1335 |  |         |      |      |    |
| NORMALIZED | 0,99 |    1 |    0 |  | 3       | 2,99 |    3 |  0 |
|            |      |      |      |  | SCORE   | 3,17 | 3,06 |  1 |

正如您所看到的分隔符";"有更好的分数。我认为对方差进行加权也很好,比发现的分隔符的平均值还要多。更有可能的是,文件中的分隔符在每行中变化不大。

这不是一个完美的解决方案,但如果你不能问分隔符是什么,它可能会对你有所帮助。

不再尝试解析为CSV,而是尝试只检索有效的电子邮件地址。我不认为空格、逗号、制表符或换行符是有效的电子邮件部分,对吧?(谁知道呢?)看看关于使用正则表达式验证电子邮件的讨论,这样你就可以看到这个解决方案的一些陷阱了。

但是,然后我会使用preg_match_all()编写正则表达式,并以有效的电子邮件格式检索所有字符串的列表。

祝你好运!

手动中的SplFileObject::getCsvControl

我直到太晚才找到它,所以写了一个功能,效果很好。如果有用/感兴趣,我的方法是:

我使用了$handle$ColName参数以及$ColName可选

$ColName允许您检查哪一个分隔符在第一条记录中找到期望的标题列名,如果csv文件有标题行的话。

如果没有标题行,或者您不知道列名,它将采用默认检查:哪个分隔符可以找到同一记录的大多数字段(通常是正确的)。然后,我还检查该分隔符是否为接下来的几行中的每一行返回相同数量的字段。

fgetcsv似乎在块中工作,并强制每个记录具有与块中的最大值相同数量的字段,因此即使每个记录的字段数量不同,也能工作

这是我的解决方案。如果你知道你期望有多少列,它就会起作用。最后,分隔符是$actual_selection_character

$separator_1=",";
$separator_2=";";
$separator_3="'t";
$separator_4=":";
$separator_5="|";
$separator_1_number=0;
$separator_2_number=0;
$separator_3_number=0;
$separator_4_number=0;
$separator_5_number=0;
/* YOU NEED TO CHANGE THIS VARIABLE */
// Expected number of separation character ( 3 colums ==> 2 sepearation caharacter / row )
$expected_separation_character_number=2;  

$file = fopen("upload/filename.csv","r");
while(! feof($file)) //read file rows
{
    $row= fgets($file);
    $row_1_replace=str_replace($separator_1,"",$row);
    $row_1_length=strlen($row)-strlen($row_1_replace);
    if(($row_1_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
    $separator_1_number=$separator_1_number+$row_1_length;
    }
    $row_2_replace=str_replace($separator_2,"",$row);
    $row_2_length=strlen($row)-strlen($row_2_replace);
    if(($row_2_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
    $separator_2_number=$separator_2_number+$row_2_length;
    }
    $row_3_replace=str_replace($separator_3,"",$row);
    $row_3_length=strlen($row)-strlen($row_3_replace);
    if(($row_3_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
    $separator_3_number=$separator_3_number+$row_3_length;
    }
    $row_4_replace=str_replace($separator_4,"",$row);
    $row_4_length=strlen($row)-strlen($row_4_replace);
    if(($row_4_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
    $separator_4_number=$separator_4_number+$row_4_length;
    }
    $row_5_replace=str_replace($separator_5,"",$row);
    $row_5_length=strlen($row)-strlen($row_5_replace);
    if(($row_5_length==$expected_separation_character_number)or($expected_separation_character_number==0)){
    $separator_5_number=$separator_5_number+$row_5_length;
    }
} // while(! feof($file))  END
fclose($file);
/* THE FILE ACTUAL SEPARATOR (delimiter) CHARACTER */
/* $actual_separation_character */
if ($separator_1_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_1;}
else if ($separator_2_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_2;}
else if ($separator_3_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_3;}
else if ($separator_4_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_4;}
else if ($separator_5_number==max($separator_1_number,$separator_2_number,$separator_3_number,$separator_4_number,$separator_5_number)){$actual_separation_character=$separator_5;}
else {$actual_separation_character=";";}
/* 
if the number of columns more than what you expect, do something ...
*/
if ($expected_separation_character_number>0){
if ($separator_1_number==0 and $separator_2_number==0 and $separator_3_number==0 and $separator_4_number==0 and $separator_5_number==0){/* do something ! more columns than expected ! */}
}