[编辑注(cmb):如果字段包含换行符,这不会产生预期的结果。]
将 CSV 文件解析为数组的便捷单行代码
<?php
$csv = array_map('str_getcsv', file('data.csv'));
?>
(PHP 5 >= 5.3.0, PHP 7, PHP 8)
str_getcsv — 将 CSV 字符串解析为数组
$string
,$separator
= ",",$enclosure
= "\"",$escape
= "\\"解析以 CSV 格式的字符串输入以查找字段,并返回包含读取的字段的数组。
注意:
此函数将考虑区域设置。如果
LC_CTYPE
为例如en_US.UTF-8
,则此函数可能无法正确读取单字节编码中的字符串。
string
要解析的字符串。
separator
设置字段分隔符(仅一个单字节字符)。
enclosure
设置字段封闭字符(仅一个单字节字符)。
escape
设置转义字符(最多一个单字节字符)。默认为反斜杠 (\
)。空字符串 (""
) 将禁用专有的转义机制。
注意: 通常,
enclosure
字符通过加倍来在字段中转义;但是,可以使用escape
字符作为替代方案。因此,对于默认参数值""
和\"
具有相同的含义。除了允许转义enclosure
字符之外,escape
字符没有特殊含义;它甚至不打算转义自身。
返回包含读取的字段的索引数组。
版本 | 描述 |
---|---|
7.4.0 | 现在,escape 参数将空字符串解释为禁用专有转义机制的信号。以前,空字符串被视为默认参数值。 |
示例 #1 str_getcsv() 示例
<?php
$string = 'PHP,Java,Python,Kotlin,Swift';
$data = str_getcsv($string);
var_dump($data);
?>
上面的示例将输出
array(5) { [0]=> string(3) "PHP" [1]=> string(4) "Java" [2]=> string(6) "Python" [3]=> string(6) "Kotlin" [4]=> string(5) "Swift" }
示例 #2 str_getcsv() 使用空字符串的示例
对于空字符串,此函数返回的值为 [null]
,而不是空数组。
<?php
$string = '';
$data = str_getcsv($string);
var_dump($data);
?>
上面的示例将输出
array(1) { [0]=> NULL }
[编辑注(cmb):如果字段包含换行符,这不会产生预期的结果。]
将 CSV 文件解析为数组的便捷单行代码
<?php
$csv = array_map('str_getcsv', file('data.csv'));
?>
基于 James 的代码行,这将创建一个关联数组数组,第一行列标题作为键。
<?php
$csv = array_map('str_getcsv', file($file));
array_walk($csv, function(&$a) use ($csv) {
$a = array_combine($csv[0], $a);
});
array_shift($csv); # 删除列标题
?>
这将产生类似的结果
[2] => Array
(
[Campaign ID] => 295095038
[Ad group ID] => 22460178158
[Keyword ID] => 3993587178
由于 str_getcsv() 与 fgetcsv() 不同,它不会解析 CSV 字符串中的行,我发现以下简单的解决方法
<?php
$Data = str_getcsv($CsvString, "\n"); // 解析行
foreach($Data as &$Row) $Row = str_getcsv($Row, ";"); // 解析行中的项目
?>
为什么不使用 explode() 而不是 str_getcsv() 来解析行?因为 explode() 不会正确处理字符串中可能包含的封闭部分或转义字符。
PHP 在解析带有字节顺序标记 (BOM) 的 UTF-8 时会失败。在将其传递给 csv 解析器之前,使用以下代码从字符串中删除它。
<?php
$bom = pack('CCC', 0xEF, 0xBB, 0xBF);
if (strncmp($yourString, $bom, 3) === 0) {
$body = substr($yourString, 3);
}
?>
正如这里的一些其他用户所指出的,如果您想遵守 RFC 或大多数电子表格工具(如 Excel 或 Google Docs),则不能使用 str_getcsv()。
这些工具不会转义逗号或换行符,而是用双引号 (") 括起字段。如果字段中存在任何双引号,则用另一个双引号转义它们(" 变为 "")。所有这些看起来可能很奇怪,但这是 RFC 和大多数工具的做法...
例如,尝试将包含换行符和逗号作为字段值一部分的 Google Docs 电子表格导出为 .csv(文件 > 另存为 > .csv),查看 .csv 内容,然后尝试使用 str_getcsv() 解析它......无论你传递什么参数,它都会失败。
以下函数可以正确处理所有内容,甚至更多
- 不使用任何 for 或 while 循环,
- 它允许使用任何分隔符(任何长度的任何字符串),
- 可选跳过空行,
- 可选修剪字段,
- 也可以处理 UTF8 数据(尽管 .csv 文件很可能是非 Unicode 的)。
以下是该函数更易于理解的版本
<?php
// 返回一个二维数组,包含行和字段
function parse_csv ($csv_string, $delimiter = ",", $skip_empty_lines = true, $trim_fields = true)
{
$enc = preg_replace('/(?<!")""/', '!!Q!!', $csv_string);
$enc = preg_replace_callback(
'/"(.*?)"/s',
function ($field) {
return urlencode(utf8_encode($field[1]));
},
$enc
);
$lines = preg_split($skip_empty_lines ? ($trim_fields ? '/( *\R)+/s' : '/\R+/s') : '/\R/s', $enc);
return array_map(
function ($line) use ($delimiter, $trim_fields) {
$fields = $trim_fields ? array_map('trim', explode($delimiter, $line)) : explode($delimiter, $line);
return array_map(
function ($field) {
return str_replace('!!Q!!', '"', utf8_decode(urldecode($field)));
},
$fields
);
},
$lines
);
}
?>
由于它不使用任何循环,所以实际上可以将其写成单行语句(单行代码)。
以下是使用一行代码作为函数体(但格式良好)的函数
<?php
// 返回与上面相同的二维数组,但使用单行代码
function parse_csv ($csv_string, $delimiter = ",", $skip_empty_lines = true, $trim_fields = true)
{
return array_map(
function ($line) use ($delimiter, $trim_fields) {
return array_map(
function ($field) {
return str_replace('!!Q!!', '"', utf8_decode(urldecode($field)));
},
$trim_fields ? array_map('trim', explode($delimiter, $line)) : explode($delimiter, $line)
);
},
preg_split(
$skip_empty_lines ? ($trim_fields ? '/( *\R)+/s' : '/\R+/s') : '/\R/s',
preg_replace_callback(
'/"(.*?)"/s',
function ($field) {
return urlencode(utf8_encode($field[1]));
},
$enc = preg_replace('/(?<!")""/', '!!Q!!', $csv_string)
)
)
);
}
?>
如果你希望,可以用其他占位符替换 !!Q!!。
玩得开心。
以下是如何将 CSV 文件快速轻松地转换为关联数组的方法
<?php
/**
* @link http://gist.github.com/385876
*/
function csv_to_array($filename='', $delimiter=',')
{
if(!file_exists($filename) || !is_readable($filename))
return FALSE;
$header = NULL;
$data = array();
if (($handle = fopen($filename, 'r')) !== FALSE)
{
while (($row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
{
if(!$header)
$header = $row;
else
$data[] = array_combine($header, $row);
}
fclose($handle);
}
return $data;
}
?>
我希望将 james at moss dot io 和 Jay Williams(csv_to_array())的两种解决方案的优点结合起来 - 从具有标题行的 CSV 文件创建关联数组。
<?php
$array = array_map('str_getcsv', file('data.csv'));
$header = array_shift($array);
array_walk($array, '_combine_array', $header);
function _combine_array(&$row, $key, $header) {
$row = array_combine($header, $row);
}
?>
然后我想,为什么不尝试进行一些基准测试呢?我拿了一个包含 50,000 行(每行 10 列)的示例 CSV 文件和 Vulcan Logic Disassembler (VLD),它连接到 Zend Engine 并转储脚本的所有操作码(执行单元) - 请参见 http://pecl.php.net/package/vld 以及此处的示例:http://fabien.potencier.org/article/8/print-vs-echo-which-one-is-faster
结果
array_walk() 和 array_map() - 39 个操作码
csv_to_array() - 69 个操作码
@normadize - 这是一个不错的起点,但它在字段为空但带引号(返回一个包含一个双引号的字符串而不是空字符串)和 """""foo""""" 应返回 ""foo"" 但改为返回 "foo" 的情况下会失败。此外,由于 CSV 中的最后一个 CRLF,我还得到一行,其中末尾有一个空字段。另外,我不太喜欢 !!Q!! 魔法或 urlencoding 来解决问题。此外,\R 在我所有的 php 安装中都不适用于 pcre。
以下是我对这方面的一些想法,没有匿名函数(因此它可以在 PHP < 5.3 上运行),也没有你的选项(因为我认为按照 RFC 的正确解析方式应该是 $skip_empty_lines = false 和 $trim_fields = false)。
// 将 CSV 文件解析为一个二维数组
// 这似乎就像用换行符和逗号拆分字符串一样简单,但这只有在执行了一些技巧的情况下才能实现
// 以确保你不会在双引号内的换行符和逗号处进行拆分。
function parse_csv($str)
{
// 匹配所有非引号文本和一个系列的引号文本(或字符串的结尾)
// 每个匹配组将使用回调进行解析,其中 $matches[1] 包含所有非引号文本,
// 以及 $matches[3] 包含引号内的所有内容
$str = preg_replace_callback('/([^"]*)("((""|[^"])*)"|$)/s', 'parse_csv_quotes', $str);
// 删除最后一个换行符以防止最后一行出现 0 字段数组
$str = preg_replace('/\n$/', '', $str);
// 以 LF 为分隔符拆分并使用回调解析每一行
return array_map('parse_csv_line', explode("\n", $str));
}
// 使用转义序列将双引号内的所有csv特殊字符替换为标记
function parse_csv_quotes($matches)
{
// 双引号内的任何内容都可能被用作稍后将字符串分割成行和字段的字符,
// 需要被引用。唯一可以保证安全使用的字符是 CR,因为它永远不会出现在未引用的文本中
// 所以我们将使用 CR 作为标记来为 CR、LF、引号和逗号创建转义序列。
$str = str_replace("\r", "\rR", $matches[3]);
$str = str_replace("\n", "\rN", $str);
$str = str_replace('""', "\rQ", $str);
$str = str_replace(',', "\rC", $str);
// 未引用的文本是允许逗号和换行符的地方,也是分割发生的地方
// 我们将从未引用的文本中删除所有 CR,将所有行尾规范化为 LF
// 这确保了 CR 仅用作引用的文本的转义序列
return preg_replace('/\r\n?/', "\n", $matches[1]) . $str;
}
// 以逗号分隔并使用回调解析每个字段
function parse_csv_line($line)
{
return array_map('parse_csv_field', explode(',', $line));
}
// 恢复作为数据一部分的任何csv特殊字符
function parse_csv_field($field) {
$field = str_replace("\rC", ',', $field);
$field = str_replace("\rQ", '"', $field);
$field = str_replace("\rN", "\n", $field);
$field = str_replace("\rR", "\r", $field);
return $field;
}
没有这个?让 fgetcsv() 来做吧。
5.1.0+
<?php
if (!function_exists('str_getcsv')) {
function str_getcsv($input, $delimiter = ",", $enclosure = '"', $escape = "\\") {
$fiveMBs = 5 * 1024 * 1024;
$fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');
fputs($fp, $input);
rewind($fp);
$data = fgetcsv($fp, 1000, $delimiter, $enclosure); // $escape 在 5.3.0 版本中添加
fclose($fp);
return $data;
}
}
?>
由于某种原因,o'connor 的代码只读了我的 csv 的一行... 我不得不替换以下行
$data = fgetcsv($fp, 1000, $delimiter, $enclosure); // $escape 在 5.3.0 版本中添加
用这个
$data;
while (!feof($fp))
{
$data[] = fgetcsv($fp, 0, $delimiter, $enclosure); // $escape 在 5.3.0 版本中添加
}
...才能从我的字符串中获取所有数据(一些粘贴到文本框并仅使用 stripslashes 处理的 POST 数据)。
在过去使用了几种方法来创建 CSV 字符串而不用文件(磁盘 IO 很糟糕)之后,我终于决定是时候编写一个函数来处理所有事情了。这个函数需要一些清理,变量类型测试可能对于需要的功能来说有点过头了,我还没有仔细考虑过。
另外,我冒昧地用字符串替换了具有某些数据类型的字段,我发现这些字符串更容易处理。你们中的一些人可能不同意这些。另外,请注意,类型“double”或 float 已专门为两位精度编码,因为如果我使用 float,它很可能是用来表示货币的。
我相信你们中的一些人会喜欢这个函数。
<?php
function str_putcsv($array, $delimiter = ',', $enclosure = '"', $terminator = "\n") {
# 首先将关联数组转换为数字索引数组
foreach ($array as $key => $value) $workArray[] = $value;
$returnString = ''; # 初始化返回字符串
$arraySize = count($workArray); # 获取数组大小
for ($i=0; $i<$arraySize; $i++) {
# 嵌套数组,处理嵌套项
if (is_array($workArray[$i])) {
$returnString .= str_putcsv($workArray[$i], $delimiter, $enclosure, $terminator);
} else {
switch (gettype($workArray[$i])) {
# 手动设置一些字符串
case "NULL": $_spFormat = ''; break;
case "boolean": $_spFormat = ($workArray[$i] == true) ? 'true': 'false'; break;
# 确保 sprintf 有一个好的数据类型可以工作
case "integer": $_spFormat = '%i'; break;
case "double": $_spFormat = '%0.2f'; break;
case "string": $_spFormat = '%s'; break;
# 未知或无效的 csv 项目 - 注意:数组的数据类型已经在上面处理过了,假设数据是嵌套的
case "object":
case "resource":
default: $_spFormat = ''; break;
}
$returnString .= sprintf('%2$s'.$_spFormat.'%2$s', $workArray[$i], $enclosure);
$returnString .= ($i < ($arraySize-1)) ? $delimiter : $terminator;
}
}
# 完成工作负载,返回输出信息
return $returnString;
}
?>
<?php
注意: 该函数会去除所有值,与 str_getcsv (v5.3) 不同。
/**
* @link https://github.com/insteps/phputils (获取更新代码)
* 将 CSV 字符串解析为 PHP 4+ 的数组。
* @param string $input 字符串
* @param string $delimiter 字符串
* @param string $enclosure 字符串
* @return array
*/
function str_getcsv4($input, $delimiter = ',', $enclosure = '"') {
if( ! preg_match("/[$enclosure]/", $input) ) {
return (array)preg_replace(array("/^\\s*/", "/\\s*$/"), '', explode($delimiter, $input));
}
$token = "##"; $token2 = "::";
// 备用标记 "\034\034", "\035\035", "%%";
$t1 = preg_replace(array("/\\\[$enclosure]/", "/$enclosure{2}/",
"/[$enclosure]\\s*[$delimiter]\\s*[$enclosure]\\s*/", "/\\s*[$enclosure]\\s*/"),
array($token2, $token2, $token, $token), trim(trim(trim($input), $enclosure)));
$a = explode($token, $t1);
foreach($a as $k=>$v) {
if ( preg_match("/^{$delimiter}/", $v) || preg_match("/{$delimiter}$/", $v) ) {
$a[$k] = trim($v, $delimiter); $a[$k] = preg_replace("/$delimiter/", "$token", $a[$k]); }
}
$a = explode($token, implode($token, $a));
return (array)preg_replace(array("/^\\s/", "/\\s$/", "/$token2/"), array('', '', $enclosure), $a);
}
if ( ! function_exists('str_getcsv')) {
function str_getcsv($input, $delimiter = ',', $enclosure = '"') {
return str_getcsv4($input, $delimiter, $enclosure);
}
}
?>
我发现自己需要解析一个 CSV 文件,但没有访问 str_getcsv 的权限,所以我写了一个 PHP < 5.3 的替代方案,希望它能帮助到那些遇到同样问题的其他人。
<?php
if (!function_exists('str_getcsv')) {
function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
if (is_string($input) && !empty($input)) {
$output = array();
$tmp = preg_split("/".$eol."/",$input);
if (is_array($tmp) && !empty($tmp)) {
while (list($line_num, $line) = each($tmp)) {
if (preg_match("/".$escape.$enclosure."/",$line)) {
while ($strlen = strlen($line)) {
$pos_delimiter = strpos($line,$delimiter);
$pos_enclosure_start = strpos($line,$enclosure);
if (
is_int($pos_delimiter) && is_int($pos_enclosure_start)
&& ($pos_enclosure_start < $pos_delimiter)
) {
$enclosed_str = substr($line,1);
$pos_enclosure_end = strpos($enclosed_str,$enclosure);
$enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
$output[$line_num][] = $enclosed_str;
$offset = $pos_enclosure_end+3;
} else {
if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
$output[$line_num][] = substr($line,0);
$offset = strlen($line);
} else {
$output[$line_num][] = substr($line,0,$pos_delimiter);
$offset = (
!empty($pos_enclosure_start)
&& ($pos_enclosure_start < $pos_delimiter)
)
?$pos_enclosure_start
:$pos_delimiter+1;
}
}
}
$line = substr($line,$offset);
}
} else {
$line = preg_split("/".$delimiter."/",$line);
/*
* 检查恼人的额外换行符,避免生成错误的行。
*/
if (is_array($line) && !empty($line[0])) {
$output[$line_num] = $line;
}
}
}
return $output;
} else {
return false;
}
} else {
return false;
}
}
}
?>
对于那些需要这个函数但尚未安装在他们环境中的用户,可以使用我下面的函数。
你可以将你的 csv 文件解析为关联数组(默认情况下)以便处理每行,或解析为对象。
<?php
function parse_csv($file, $options = null) {
$delimiter = empty($options['delimiter']) ? "," : $options['delimiter'];
$to_object = empty($options['to_object']) ? false : true;
$str = file_get_contents($file);
$lines = explode("\n", $str);
pr($lines);
$field_names = explode($delimiter, array_shift($lines));
foreach ($lines as $line) {
// Skip the empty line
if (empty($line)) continue;
$fields = explode($delimiter, $line);
$_res = $to_object ? new stdClass : array();
foreach ($field_names as $key => $f) {
if ($to_object) {
$_res->{$f} = $fields[$key];
} else {
$_res[$f] = $fields[$key];
}
}
$res[] = $_res;
}
return $res;
}
?>
注意
csv 文件的第一行将被视为标题(字段名称)。
待办事项
- 处理封闭符
- 处理转义字符
- 其他功能/增强功能,根据您的需要
示例用法
/path/to/file.csv 文件内容
CODE,COUNTRY
AD,Andorra
AE,United Arab Emirates
AF,Afghanistan
AG,Antigua and Barbuda
<?php
$arr_csv = parse_csv("/path/to/file.csv");
print_r($arr_csv);
?>
// 输出
数组
(
[0] => 数组
(
[CODE] => AD
[COUNTRY] => Andorra
)
[1] => 数组
(
[CODE] => AE
[COUNTRY] => United Arab Emirates
)
[2] => Array
(
[CODE] => AF
[COUNTRY] => Afghanistan
)
[3] => 数组
(
[CODE] => AG
[COUNTRY] => Antigua and Barbuda
)
)
<?php
$obj_csv = parse_csv("/path/to/file.csv", array("to_object" => true));
print_r($obj_csv);
?>
// 输出
数组
(
[0] => stdClass 对象
(
[CODE] => AD
[COUNTRY] => Andorra
)
[1] => stdClass 对象
(
[CODE] => AE
[COUNTRY] => United Arab Emirates
)
[2] => stdClass 对象
(
[CODE] => AF
[COUNTRY] => Afghanistan
)
[3] => stdClass 对象
(
[CODE] => AG
[COUNTRY] => Antigua and Barbuda
)
[4] => stdClass 对象
(
[CODE] =>
[COUNTRY] =>
)
)
// 如果您在 csv 文件中使用字符 |(管道)作为分隔符,请使用
<?php
$arr_csv = parse_csv("/path/to/file.csv", array("delimiter"=>"|"));
?>
==NSD==
如何解决 UTF-8 BOM 问题
如何处理UTF-8编码的CSV文件中的BOM问题
$bom =( chr(0xEF) . chr(0xBB) . chr(0xBF) ); // 定义 bom
$f = file_get_contents('a.csv'); // 打开 CSV 文件
#$csv = str_getcsv($f); // 会出现 bom 问题
$csv = str_getcsv(str_replace($bom,'',$f)); // 替换 bom
var_dump($csv); // 输出
我写了这段代码来处理
- 有无封闭符的字段;
- 使用相同字符的转义和封闭字符(例如 Excel 中的 <<">>)
<?php
/**
* 将 csv 文件转换为行和列数组。
* [email protected]
* @param $fileContent 字符串
* @param string $escape 字符串
* @param string $enclosure 字符串
* @param string $delimiter 字符串
* @return array
*/
function csvToArray($fileContent,$escape = '\\', $enclosure = '"', $delimiter = ';')
{
$lines = array();
$fields = array();
if($escape == $enclosure)
{
$escape = '\\';
$fileContent = str_replace(array('\\',$enclosure.$enclosure,"\r\n","\r"),
array('\\\\',$escape.$enclosure,"\\n","\\n"),$fileContent);
}
else
$fileContent = str_replace(array("\r\n","\r"),array("\\n","\\n"),$fileContent);
$nb = strlen($fileContent);
$field = '';
$inEnclosure = false;
$previous = '';
for($i = 0;$i<$nb; $i++)
{
$c = $fileContent[$i];
if($c === $enclosure)
{
if($previous !== $escape)
$inEnclosure ^= true;
else
$field .= $enclosure;
}
else if($c === $escape)
{
$next = $fileContent[$i+1];
if($next != $enclosure && $next != $escape)
$field .= $escape;
}
else if($c === $delimiter)
{
if($inEnclosure)
$field .= $delimiter;
else
{
// 字段结束
$fields[] = $field;
$field = '';
}
}
else if($c === "\n")
{
$fields[] = $field;
$field = '';
$lines[] = $fields;
$fields = array();
}
else
$field .= $c;
$previous = $c;
}
// 添加最后一个元素
if(true || $field !== '')
{
$fields[] = $field;
$lines[] = $fields;
}
return $lines;
}
?>
> 49 durik at 3ilab dot net / 4 年前
$rows = str_getcsv($csv_data, "\n");
- 错误,csv 中的数据可能包含 "\n"
'aaa','bb
b','ccc'
从 daniel dot oconnor at gmail dot com 中汲取灵感,这里有一个替代的 str_putcsv() 函数,它利用现有的 PHP 核心功能 (5.1.0+) 来避免重新造轮子。
<?php
if(!function_exists('str_putcsv')) {
function str_putcsv($input, $delimiter = ',', $enclosure = '"') {
// 打开一个用于读写的内存“文件”...
$fp = fopen('php://temp', 'r+');
// ... 使用 fputcsv() 将 $input 数组写入“文件”...
fputcsv($fp, $input, $delimiter, $enclosure);
// ... 重置“文件”以使我们可以读取我们刚刚写入的内容...
rewind($fp);
// ... 将整行读入变量...
$data = fgets($fp);
// ... 关闭“文件”...
fclose($fp);
// ... 并将 $data 返回给调用者,并删除 fgets() 中的尾随换行符。
return rtrim( $data, "\n" );
}
}
?>
旧的 MacOS(直到大约 2001 年)和旧的 Office For MacOS(直到 2007 年?我认为)使用回车符作为换行符,
Microsoft Windows 使用回车符+换行符作为换行符,
Unix(Linux 和现代 MacOS)使用换行符,
某些系统使用 BOM/字节序掩码来表示它们使用 UTF-8,我甚至遇到过每个 CSV 行一个 BOM 的情况!
为了处理上述所有情况的 csv 文件解析器,我编写了以下代码
<?php
function parse_csv(string $csv, string $separator = ","): array
{
$csv = strtr(
$csv,
[
"\xEF\xBB\xBF" => "", // 删除 UTF-8 字节序掩码(如果存在)
"\r\n" => "\n", // Windows CrLf=> Unix Lf
"\r" => "\n" // 旧的 MacOS Cr => Unix Lf
// (现代 MacOS 和 Linux 都使用 Lf .. Windows 是唯一的例外)
]
);
$lines = explode("\n", $csv);
$keys = str_getcsv(array_shift($lines), $separator);
$ret = array();
foreach ($lines as $lineno => $line) {
if (strlen($line) < 1) {
// ... 可能格式错误的 csv,但我们会允许它
continue;
}
$parsed = str_getcsv($line, $separator);
if (count($parsed) !== count($keys)) {
throw new \RuntimeException("csv 行 #{$lineno} 错误:计数不匹配:" . count($parsed) . ' !== ' . count($keys) . ": " . var_export([
'error' => 'count mismatch',
'keys' => $keys,
'parsed' => $parsed,
'line' => $line
], true));
}
$ret[] = array_combine($keys, $parsed);
}
return $ret;
}
?>
我在描述中没有看到这一点,但似乎字段将被稍微修剪掉尾随的换行符。
在以下示例中
<?php
$string = "\nPHP\r\n,Java\nScript\r\n\r\n,Fortran\n,Cobol\n\n,\nSwift\r\n\r\n\r\n";
$data = str_getcsv($string);
foreach($data as $d) print "[$d]";
/* 结果:
================================================
[
PHP][Java
Script
][Fortran][Cobol
][
Swift
]
================================================ */
?>
你会看到
- 保留了开头的换行符;字段中剩余的换行符也被保留
- 删除了一个尾随换行符;任何更多的都被保留
- 字符串末尾的换行符也被删除;这意味着删除了字符串末尾的两个尾随换行符
- 换行符可以是 unix/macos 换行符(\n)或 windows 换行符(\r\n)
在 Macintosh 上测试,所以我不确定这是否普遍适用。
除其他事项外,这意味着您可以使用 file() 函数读取文件,而不必包含 FILE_IGNORE_NEW_LINES 标志。
为了完整起见,这里有一个用户空间的 str_putcsv(),它与 fgetcsv() 和 fputcsv() 的参数完全兼容。即 $escape 和 $eol,而其他所有似乎都省略了它们。
<?php
function str_putcsv(
array $fields,
string $separator = ",",
string $enclosure = "\"",
string $escape = "\\",
string $eol = "\n"
) {
return implode($separator,
array_map(
function($a)use($enclosure, $escape) {
$type = gettype($a);
switch($type) {
case 'integer': return sprintf('%d', $a);
case 'double': return rtrim(sprintf('%0.'.ini_get('precision').'f', $a), '0');
case 'boolean': return ( $a ? 'true' : 'false' );
case 'NULL': return '';
case 'string':
return sprintf('"%s"', str_replace(
[$escape, $enclosure],
[$escape.$escape, $escape.$enclosure],
$a
));
default: throw new TypeError("无法将类型字符串化: $type");
}
},
$fields
)
) . $eol;
}
想象一下,你需要一个能够同时处理 URL 和逗号分隔文本的函数。
这正是使用 str_getcsv() 的函数,它可以像这样工作。只需插入一个 CSV URL 或逗号分隔文本,它就可以正常工作。
<?php
function parse_csv( $filename_or_text, $delimiter=',', $enclosure='"', $linebreak="\n" )
{
$return = array();
if(false !== ($csv = (filter_var($filename_or_text, FILTER_VALIDATE_URL) ? file_get_contents($filename_or_text) : $filename_or_text)))
{
$csv = trim($csv);
$csv = mb_convert_encoding($csv, 'UTF-16LE');
foreach(str_getcsv($csv, $linebreak, $enclosure) as $row){
$col = str_getcsv($row, $delimiter, $enclosure);
$col = array_map('trim', $col);
$return[] = $col;
}
}
else
{
throw new \Exception('Can not open the file.');
$return = false;
}
return $return;
}
?>
有时 str_getcsv 函数的 enclosure 参数不起作用,所以我写了一个等效于该函数的函数
<?php
/**
* @param string $input
* @param string $delimiter
* @param string $enclosure
* @param string $escape
* @return array
* @author TXX
* @date 2021/1/25 15:03
*/
function my_str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\') {
$output = array();
if (empty($input) || !is_string($input)) {
return $output;
}
if (preg_match("/". $escape . $enclosure ."/", $input)) {
while ($strlen = strlen($input)) {
$pos_delimiter = strpos($input, $delimiter); //分隔符出现位置
$pos_enclosure_start = strpos($input, $enclosure); //封闭符-开始出现位置
//有封闭符并封闭符在分隔符之前
if (is_int($pos_delimiter) && is_int($pos_enclosure_start) && $pos_enclosure_start < $pos_delimiter) {
$pos_enclosure_start += 1;
$enclosed_str = substr($input, $pos_enclosure_start); //封闭字符串-开始
$pos_enclosure_end = strpos($enclosed_str, $enclosure); //封闭符-结尾封闭字符串-开始中出现位置
$pos_enclosure_end += $pos_enclosure_start; //封闭符-结尾在原始数据中出现位置
if ($pos_enclosure_end < $pos_delimiter) {
//封闭符-结束在分隔符之前,无需进行封闭
$output[] = substr($input, 0, $pos_delimiter);
$offset = $pos_delimiter + 1;
} else {
//封闭符-结束在分隔符之后,需要封闭
$pos_enclosure_end += 1;
$before_enclosed_str = substr($input, 0, $pos_enclosure_end);
$enclosed_str = substr($input, $pos_enclosure_end); //封闭字符串之后的字符串
$enclosed_arr = my_str_getcsv($enclosed_str, $delimiter, $enclosure); //将封闭之后的字符串执行自身
$enclosed_arr[0] = $before_enclosed_str . $enclosed_arr[0];
$output = array_merge($output, $enclosed_arr);
$offset = strlen($input); //光标移至结尾
}
} else {
//无封闭
if (!is_int($pos_delimiter)) {
//无分隔符,直接将字符串加入输出数组
$output[] = $input;
//光标移至结尾
$offset = strlen($input);
} else if ($input == $delimiter) {
//如果字符串只剩下分隔符,需保存'',''
$output = array_merge($output, ['','']);
$offset = $pos_delimiter+1; //光标移至分隔符后一位
} else {
$output[] = substr($input, 0, $pos_delimiter); //将分割符之前的数据
$offset = $pos_delimiter+1; //光标移至分隔符后一位
}
}
//将字符串更新至光标位置
$input = substr($input,$offset);
}
} else {
//字符串中不存在封闭符,直接通过分隔符分割
$input = preg_split("/". $escape . $delimiter ."/", $input);
if (is_array($input)) {
$output = $input;
}
}
return $output;
}
?>
请注意,该函数不会删除转义字符。如果您执行
<?php
str_getcsv('"abc\"abc"')
?>
您将得到一个包含字符串(8) "abc\"abc" 的数组,\ 将保留。
public function csv_to_array($filename = '', $delimiter = ',', $boolean_include_title_row = false, $field_names = array()){
try {
if (!file_exists($filename) || !is_readable($filename)) {
return false;
}
if (is_array($field_names) && !empty($field_names)) {
$header = $field_names;
} elseif (is_string($field_names) && (strlen($field_names) > 0)) {
$header = explode(",", $field_names);
} else {
$header = null;
}
$csv = array_map('str_getcsv', file($filename));
$data = array();
foreach ($csv as $key => $row) {
$data[] = array_combine($header, $row);
}
if (!$boolean_include_title_row) {
unset($data[0]);
$data = array_values($data);
}
return $data;
} catch (Exception $e) {
return false;
}
}
我准备了一些更好的解析 CSV 字符串的函数。
function csv_to_array($string='', $row_delimiter=PHP_EOL, $delimiter = "," , $enclosure = '"' , $escape = "\\" )
{
$rows = array_filter(explode($row_delimiter, $string));
$header = NULL;
$data = array();
foreach($rows as $row)
{
$row = str_getcsv ($row, $delimiter, $enclosure , $escape);
if(!$header)
$header = $row;
else
$data[] = array_combine($header, $row);
}
return $data;
}
注意:与 str_getcsv (v5.3) 不同,该函数会修剪所有值。
/**
* @link https://github.com/insteps/phputils (for updated code)
* 将 CSV 字符串解析为 php 4+ 的数组。
* @param string $input 字符串
* @param string $delimiter 字符串
* @param string $enclosure 字符串
* @return array
*/
function str_getcsv4($input, $delimiter = ',', $enclosure = '"') {
if( ! preg_match("/[$enclosure]/", $input) ) {
return (array)preg_replace(array("/^\\s*/", "/\\s*$/"), '', explode($delimiter, $input));
}
$token = "##"; $token2 = "::";
//alternate tokens "\034\034", "\035\035", "%%";
$t1 = preg_replace(array("/\\\[$enclosure]/", "/$enclosure{2}/",
"/[$enclosure]\\s*[$delimiter]\\s*[$enclosure]\\s*/", "/\\s*[$enclosure]\\s*/"),
array($token2, $token2, $token, $token), trim(trim(trim($input), $enclosure)));
$a = explode($token, $t1);
foreach($a as $k=>$v) {
if ( preg_match("/^{$delimiter}/", $v) || preg_match("/{$delimiter}$/", $v) ) {
$a[$k] = trim($v, $delimiter); $a[$k] = preg_replace("/$delimiter/", "$token", $a[$k]); }
}
$a = explode($token, implode($token, $a));
return (array)preg_replace(array("/^\\s/", "/\\s$/", "/$token2/"), array('', '', $enclosure), $a);
}
if ( ! function_exists('str_getcsv')) {
function str_getcsv($input, $delimiter = ',', $enclosure = '"') {
return str_getcsv4($input, $delimiter, $enclosure);
}
}
请注意,此函数也可以用于解析其他类型的结构。例如,我用它来解析 .htaccess AddDescription 行
AddDescription "My description to the file." filename.jpg
这些行可以这样解析
<?php
$line = 'AddDescription "My description to the file." filename.jpg';
$parsed = str_getcsv(
$line, # 输入行
' ', # 分隔符
'"', # 围合符
'\\' # 转义符
);
var_dump( $parsed );
?>
输出
array(3) {
[0]=>
string(14) "AddDescription"
[1]=>
string(27) "My description to the file."
[2]=>
string(12) "filename.jpg"
}
如果您对使用多维数组感到满意,此方法应该可以正常工作。我原本想使用 keananda 提供的方法,但它在 pr($lines) 上卡住了。
<?php
function f_parse_csv($file, $longest, $delimiter) {
$mdarray = array();
$file = fopen($file, "r");
while ($line = fgetcsv($file, $longest, $delimiter)) {
array_push($mdarray, $line);
}
fclose($file);
return $mdarray;
}
?>
$longest 是一个数字,表示 csv 文件中最长的行,如 fgetcsv() 所需。fgetcsv() 的页面中提到最长的行可以设置为 0 或省略,但我发现如果不设置的话,它无法正常工作。因此,当必须使用它时,我只是将它设置得很大。
RFC 4180 处理 CSV 的规范,指出转义字符应该是一个双引号:(第 2 页)
7. 如果使用双引号来包含字段,则
出现在字段内的双引号必须用
另一个双引号进行转义。例如
"aaa","b""bb","ccc"
我总是使用这个
function convert_to_csv($input_array, $output_file_name, $delimiter)
{
/** 将原始内存作为文件打开,无需临时文件 */
$temp_memory = fopen('php://memory', 'w');
/** 遍历数组 */
foreach ($input_array as $line) {
/** 默认的 php csv 处理程序 **/
fputcsv($temp_memory, $line, $delimiter);
}
/** 重置“文件”,以包含 csv 行 **/
fseek($temp_memory, 0);
/** 修改标头,以便下载 csv 文件 **/
header('Content-Type: application/csv');
header('Content-Disposition: attachement; filename="' . $output_file_name . '";');
/** 将文件发送到浏览器以供下载 */
fpassthru($temp_memory);
}
/** 要转换为 csv 的数组 */
$array_to_csv = Array(Array(12566, 'Enmanuel', 'Corvo'), Array(56544, 'John', 'Doe'), Array(78550, 'Mark', 'Smith'));
convert_to_csv($array_to_csv, 'report.csv', ',');
您可以在此处阅读完整的文章
<a href="http://webtricksandtreats.com/export-to-csv-php/">PHP to CSV Download </a>
str_getcsv 对尾随空格非常敏感 - 例如,它可能无法识别用引号分隔的字符串集中最后一个字符串后面带有空格的最终元素。在使用 str_getcsv() 之前使用 trim() 可以快速解决此问题。
对 JayWilliams 的函数进行了优化
<?php
function csv_to_array($filename, $delimiter=',', $enclosure='"', $escape = '\\')
{
if(!file_exists($filename) || !is_readable($filename)) return false;
$header = null;
$data = array();
$lines = file($filename);
foreach($lines as $line) {
$values = str_getcsv($line, $delimiter, $enclosure, $escape);
if(!$header) $header = $values;
else $data[] = array_combine($header, $values);
}
return $data;
}
?>
快速且正确的方法
$temp = fopen('php://temp', 'r+');
fputs($csvString, $temp);
rewind($temp);
$csvArray = array();
while( $csvRow = fgetcsv($temp) )
$csvArray[] = $csvRow;
fclose($temp);
这是一个将多行 CSV 字符串转换为数组的小函数
<?php
function csv_to_array($csv, $delimiter = ',', $enclosure = '"', $escape = '\\', $terminator = "\n") {
$r = array();
$rows = explode($terminator,trim($csv));
$names = array_shift($rows);
$names = str_getcsv($names,$delimiter,$enclosure,$escape);
$nc = count($names);
foreach ($rows as $row) {
if (trim($row)) {
$values = str_getcsv($row,$delimiter,$enclosure,$escape);
if (!$values) $values = array_fill(0,$nc,null);
$r[] = array_combine($names,$values);
}
}
return $r;
}
?>
`durik at 3ilab dot net` 提出了一个很好的观点,但提供的解决方案在某些(非常罕见的)边缘情况下可能会失败。我相信更完美的解决方案如下
<?php
// 使用 I/O 流而不是实际文件。
$handle = fopen('php://temp/myCSV', 'w+b');
// 将所有数据写入其中
fwrite($handle, $CSVString);
// 重置以进行读取
rewind($handle);
// 使用 fgetcsv,它在某些情况下比 str_getcsv 更好地工作
$rows = array();
while ($row = fgetcsv($handle)) $rows[] = $row;
?>
此技术的变体也可以用来实现 PHP 缺少的“str_putcsv”。
改进 James 的代码,现在允许设置分隔符。
$csv = array_map(function($v){return str_getcsv($v, ';');}, file('data.csv'));
您好,
我想使用 php 代码在 csv 文件中设置公式。当我下载 csv 文件时,应插入公式(来自数据库的数据),并且当我们输入列值时,应根据公式计算值。
请帮忙。
谢谢。