在PHP中确定.csv分隔符

3
注意:我要先说一句,我知道自己可能错过了一些非常显而易见的东西。我正在编码迷雾中,看不到简单的解决方案。
问题:我已经用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
    )
)

就像我说的一样,我知道这可能不是正确的方法来处理问题,所以非常感谢您提供的任何见解!


1
你可以添加另一个输入,允许用户指定分隔符。 - Supericy
我认为@Supericy的想法是最好的,这似乎不是一件容易确定的事情,除非你有某种基础可以开始。 - Brandon Buck
重复:https://dev59.com/fHA75IYBdhLWcg3wP2kg - Richard EB
5个回答

3
用易用性而非代码来解决这个问题。让用户选择分隔符。
然而,由于他们可能不知道什么是制表符分隔、CSV等,只需向他们展示预览。他们可以从选项中选择,直到输出看起来正确且制表符形式。
接着,根据所选格式进行解析。

2
我将展示一个可能是相当好的解决方案的算法,不要认为这个问题很容易,这就像猜测一样,因此这个问题没有完美的解决方案。
相反,应该尝试使用统计学或其他启发式方法来近似一个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 |

如您所见,分隔符“;”得分更高。我认为更加注重分隔符的方差而非平均值也是不错的选择。因为在一个文件中,每行的分隔符变化不大的情况更为常见。

1
这不是一个完美的解决方案,但如果您无法询问分隔符,它可能会帮助您。不再尝试解析CSV,而是尝试获取有效的电子邮件地址。我认为空格、逗号、制表符或换行符不是有效的电子邮件部分,对吧?(谁知道呢;) 查看使用正则表达式验证电子邮件的讨论-这样您就可以看到此解决方案的一些缺点。然后,我将使用preg_match_all()编写正则表达式,并检索所有格式正确的字符串列表。祝你好运!

1

SplFileObject::getCsvControl在手册中。

我发现它太晚了,所以写了一个很好用的函数。如果有用/感兴趣,我的方法是:

我使用$handle$ColName参数,其中$ColName是可选的。

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

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

fgetcsv似乎按块工作,并强制每个记录具有与块中最大值相同的字段数,因此即使每个记录的字段数不同,也可以正常工作。


请注意,此函数不能通过给定文件自动猜测CSV控制方式,而是返回之前使用SplFileObject :: setCsvControl()设置的内容。 - David Lemon

0
这是我的解决方案。 如果您知道期望的列数,它就能正常工作。 最后,分隔符字符是$actual_separation_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 ! */}
}

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