[編者註 (cmb):如果欄位包含換行符號,則不會產生所需的結果。]
將 CSV 檔案解析為陣列的簡便單行指令
<?php
$csv = array_map('str_getcsv', file('data.csv'));
?>
(PHP 5 >= 5.3.0, PHP 7, PHP 8)
str_getcsv — 將 CSV 字串解析為陣列
解析 CSV(逗號分隔值)格式的字串輸入,並返回一個包含讀取欄位的陣列。
注意:此函式會考慮地區設定。例如,如果
LC_CTYPE
為en_US.UTF-8
,則某些單一位元組編碼的資料可能會被錯誤地解析。
字串 (string)
要解析的字串。
分隔符號 (separator)
separator
參數設定欄位分隔符號。它必須是單一位元組字元。
括住字元 (enclosure)
enclosure
參數設定欄位括住字元。它必須是單一位元組字元。
跳脫字元 (escape)
escape
參數設定跳脫字元。它必須是單一位元組字元或空字串。空字串 (""
) 會停用專有的跳脫機制。
注意:通常在欄位內,
enclosure
字元會透過重複自身來跳脫;但是,escape
字元可以用作替代方案。因此,對於預設參數值,""
和\"
具有相同的含義。除了允許跳脫enclosure
字元之外,escape
字元沒有特殊含義;它甚至不 meant to 跳脫自身。
從 PHP 8.4.0 開始,依賴 escape
的預設值已被棄用。需要明確地提供它,無論是透過位置參數還是使用命名參數。
當 escape
設定為非空字串 (""
) 時,可能會導致 CSV 不符合 » RFC 4180 或無法透過 PHP CSV 函式來回轉換。 escape
的預設值是 "\\"
,因此建議將其明確設定為空字串。預設值將在未來的 PHP 版本中更改,不早於 PHP 9.0。
返回一個包含已讀取欄位的索引陣列。
版本 | 說明 |
---|---|
8.4.0 | PHP 8.4.0 |
8.4.0 | 依賴 escape 的預設值現已被棄用。 |
8.3.0 | 如果 separator 、enclosure 或 escape 無效,現在會拋出 ValueError。這模仿了 fgetcsv() 和 fputcsv() 的行為。 |
7.4.0 | PHP 8.0.0 |
PHP 7.4.0
escape
參數現在將空字串解釋為停用專有跳脫機制的訊號。以前,空字串被視為預設參數值。
範例
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] => 陣列
(
[廣告活動 ID] => 295095038
[廣告群組 ID] => 22460178158
[關鍵字 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 解析器之前,請使用以下程式碼將 BOM 從字串中移除
<?php
$bom = pack('CCC', 0xEF, 0xBB, 0xBF);
if (strncmp($yourString, $bom, 3) === 0) {
$body = substr($yourString, 3);
}
?>
如同其他使用者所提到的,如果您想要符合 RFC 或大多數試算表工具(例如 Excel 或 Google 試算表)的規範,則無法使用 str_getcsv()。
這些工具不會跳脫逗號或換行符號,而是在欄位前後加上雙引號 (")。如果欄位中包含雙引號,則會使用另一個雙引號進行跳脫 (" 變為 "")。這看起來可能很奇怪,但這正是 RFC 和大多數工具的做法…
例如,嘗試以 .csv 格式匯出 Google 試算表(檔案 > 下載為 > .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
);
}
?>
由於這沒有使用任何迴圈,您實際上可以將它寫成單行程式碼 (one-liner)。
以下是僅使用一行代碼作為函式主體的函式,但經過良好的格式化
<?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
*/
函式 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 個操作碼
沒有這個嗎?請 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;
}
}
?>
@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);
//未加引號的文字允許逗號和換行符號,並且會在此處進行分割
//我們將透過將所有行尾正規化為 LF 來移除未加引號文字中的所有 CR
//這確保 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;
}
<?php
Note: The function trims all values unlike str_getcsv (v5.3).
/**
* @link https://github.com/insteps/phputils (for updated code)
* Parse a CSV string into an array for php 4+.
* @param string $input String
* @param string $delimiter String
* @param string $enclosure String
* @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);
}
}
?>
過去我使用過幾種方法來建立 CSV 字串而不使用檔案(磁碟 IO 很爛),我最終決定是時候編寫一個函式來處理所有這些事情。這個函式可以進行一些清理,而且變數類型測試對於所需的功能來說可能有點過頭了,我還沒有想太多。
此外,我擅自將具有特定資料類型的欄位替換為我覺得更容易使用的字串。你們中的一些人可能不同意這些。另外,請注意「double」或浮點數類型已特別編碼為兩位數精度,因為如果我使用浮點數,它很可能是用於貨幣。
我相信你們當中的一些人會欣賞這個函式。
<?php
function str_putcsv($array, $delimiter = ',', $enclosure = '"', $terminator = "\n") {
# First convert associative array to numeric indexed array
foreach ($array as $key => $value) $workArray[] = $value;
$returnString = ''; # Initialize return string
$arraySize = count($workArray); # Get size of array
for ($i=0; $i<$arraySize; $i++) {
# Nested array, process nest item
if (is_array($workArray[$i])) {
$returnString .= str_putcsv($workArray[$i], $delimiter, $enclosure, $terminator);
} else {
switch (gettype($workArray[$i])) {
# Manually set some strings
case "NULL": $_spFormat = ''; break;
case "boolean": $_spFormat = ($workArray[$i] == true) ? 'true': 'false'; break;
# Make sure sprintf has a good datatype to work with
case "integer": $_spFormat = '%i'; break;
case "double": $_spFormat = '%0.2f'; break;
case "string": $_spFormat = '%s'; break;
# Unknown or invalid items for a csv - note: the datatype of array is already handled above, assuming the data is nested
case "object":
case "resource":
default: $_spFormat = ''; break;
}
$returnString .= sprintf('%2$s'.$_spFormat.'%2$s', $workArray[$i], $enclosure);
$returnString .= ($i < ($arraySize-1)) ? $delimiter : $terminator;
}
}
# Done the workload, return the output information
return $returnString;
}
?>
對於那些需要這個函式但尚未安裝在他們的環境中的人,您可以使用我的以下函式。
您可以將 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) {
// 略過空行
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] => 陣列
(
[CODE] => AF
[國家] => 阿富汗
)
[3] => 陣列
(
[代碼] => AG
[國家] => 安地瓜和巴布達
)
)
<?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
[國家] => 阿富汗
)
[3] => stdClass 物件
(
[代碼] => AG
[國家] => 安地瓜和巴布達
)
[4] => stdClass 物件
(
[代碼] =>
[國家] =>
)
)
// 如果您在 CSV 檔案中使用字元 | (管線符號) 作為分界符號,請使用
<?php
$arr_csv = parse_csv("/path/to/file.csv", array("delimiter"=>"|"));
?>
==NSD==
我發現我需要解析 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);
/*
* Validating against pesky extra line breaks creating false rows.
*/
if (is_array($line) && !empty($line[0])) {
$output[$line_num] = $line;
}
}
}
return $output;
} else {
return false;
}
} else {
return false;
}
}
}
?>
如何解決 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); //輸出
由於某些原因,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 資料)。
我寫了這段程式碼來處理
- 有或沒有引號的欄位;
- 使用相同字元作為跳脫字元和引號字元(例如 Excel 中的 <<">>)
<?php
/**
* Converts a csv file into an array of lines and columns.
* khelibert@gmail.com
* @param $fileContent String
* @param string $escape String
* @param string $enclosure String
* @param string $delimiter String
* @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
{
//end of the field
$fields[] = $field;
$field = '';
}
}
else if($c === "\n")
{
$fields[] = $field;
$field = '';
$lines[] = $fields;
$fields = array();
}
else
$field .= $c;
$previous = $c;
}
//we add the last element
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'
舊版 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' => '計數不符',
'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 旗標。
為了與標準 (RFC-4180) CSV 檔案達到最大相容性,請記住應停用專有的跳脫機制。 也就是說,將第五個可選參數設定為 ""(空字串)。
為了完整性,這裡有一個使用者空間的 str_putcsv(),它與 fgetcsv() 和 fputcsv() 的參數完全相容。 也就是 $escape 和 $eol,其他所有函式似乎都省略了這些參數。
<?php
函數 str_putcsv(
陣列 $fields,
字串 $separator = ",",
字串 $enclosure = "\"",
字串 $escape = "\\",
字串 $eol = "\n"
) {
返回 implode($separator,
array_map(
函數($a) 使用($enclosure, $escape) {
$type = gettype($a);
根據($type) {
案例 'integer': 返回 sprintf('%d', $a);
案例 'double': 返回 rtrim(sprintf('%0.'.ini_get('precision').'f', $a), '0');
案例 'boolean': 返回 ( $a ? 'true' : 'false' );
案例 'NULL': 返回 '';
案例 'string':
返回 sprintf('"%s"', str_replace(
[$escape, $enclosure],
[$escape.$escape, $escape.$enclosure],
$a
));
預設: 拋出新的 TypeError("無法字串化類型: $type");
}
},
$fields
)
) . $eol;
}
想像一下,您需要一個同時適用於網址和逗號分隔文字的函數。
這正是使用 str_getcsv() 的函數。只需插入 CSV 網址或逗號分隔的文字,它就能正常運作。
<?php
函式 parse_csv( $filename_or_text, $delimiter=',', $enclosure='"', $linebreak="\n" )
{
$return = 陣列();
如果(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');
對每個(str_getcsv($csv, $linebreak, $enclosure) 作為 $row){
$col = str_getcsv($row, $delimiter, $enclosure);
$col = array_map('trim', $col);
$return[] = $col;
}
}
否則
{
拋出新的 \Exception('無法開啟檔案。');
$return = false;
}
返回 $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;
}
?>
我準備了一個更好的函式來解析 CSV 字串。
函式 csv_to_array($string='', $row_delimiter=PHP_EOL, $delimiter = "," , $enclosure = '"' , $escape = "\\" )
{
$rows = array_filter(explode($row_delimiter, $string));
$header = NULL;
$data = 陣列();
對每個($rows 作為 $row)
{
$row = str_getcsv ($row, $delimiter, $enclosure , $escape);
如果( !$header )
$header = $row;
否則
$data[] = array_combine($header, $row);
}
返回 $data;
}
注意:與 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);
}
}
請注意,這個函式也可以用來解析其他類型的結構。例如,我曾用它來解析 .htaccess 的 AddDescription 行
AddDescription "我的檔案描述。" filename.jpg
這些行可以像這樣解析
<?php
$line = 'AddDescription "我的檔案描述。" filename.jpg';
$parsed = str_getcsv(
$line, # 輸入行
' ', # 分隔符號
'"', # 包含符號
'\\' # 跳脫字元
);
var_dump( $parsed );
?>
輸出
array(3) {
[0]=>
string(14) "AddDescription"
[1]=>
string(27) "我的檔案描述。"
[2]=>
string(12) "filename.jpg"
}
從 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" );
}
}
?>
如果您只想要一個多維陣列,這樣就可以了。我原本想用 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 或省略,但我沒辦法這樣做。當我必須使用它時,我只是將它設定得非常大。