我正在尝试使用PHP解析一组CSV数据,但存在一个主要问题。其中一个字段是长描述字段,其本身包含机箱内的换行符。
我的主要问题是编写一段可以逐行拆分数据的代码,但也可以识别不应该使用数据中的换行符的时间。此字段中的换行符未正确转义,因此很难与合法的换行符区分开来。
我试图提出一个可以正确处理它的正则表达式,但到目前为止没有运气。有什么想法吗?
CSV格式:
"####","text data here", "text data \n with linebreaks \n here"\n
"####","more text data", "more data \n with \n linebreaks \n here"\n
答案 0 :(得分:10)
根据aleske,PHP fgetcsv函数文档中的评论者:
PHP的CSV处理内容是非标准的,与RFC4180相矛盾,因此fgetcsv()无法正确处理[包含换行符]的文件......
他提供了以下功能来解决这个限制:
function csvstring_to_array(&$string, $CSV_SEPARATOR = ';', $CSV_ENCLOSURE = '"', $CSV_LINEBREAK = "\n") {
$o = array();
$cnt = strlen($string);
$esc = false;
$escesc = false;
$num = 0;
$i = 0;
while ($i < $cnt) {
$s = $string[$i];
if ($s == $CSV_LINEBREAK) {
if ($esc) {
$o[$num] .= $s;
} else {
$i++;
break;
}
} elseif ($s == $CSV_SEPARATOR) {
if ($esc) {
$o[$num] .= $s;
} else {
$num++;
$esc = false;
$escesc = false;
}
} elseif ($s == $CSV_ENCLOSURE) {
if ($escesc) {
$o[$num] .= $CSV_ENCLOSURE;
$escesc = false;
}
if ($esc) {
$esc = false;
$escesc = true;
} else {
$esc = true;
$escesc = false;
}
} else {
if ($escesc) {
$o[$num] .= $CSV_ENCLOSURE;
$escesc = false;
}
$o[$num] .= $s;
}
$i++;
}
// $string = substr($string, $i);
return $o;
}
看起来它会起作用。
答案 1 :(得分:4)
我发现在将CSV转换为unix格式后,您可以使用普通的CSV解析器。
这是一个为我提供技巧的功能。
function dos2unix($s) {
$s = str_replace("\r\n", "\n", $s);
$s = str_replace("\r", "\n", $s);
$s = preg_replace("/\n{2,}/", "\n\n", $s);
return $s;
}
解析函数
function csvstring_to_array($string, $separatorChar = ',', $enclosureChar = '"', $newlineChar = PHP_EOL) {
// @author: Klemen Nagode
$string = dos2unix($string);
$array = array();
$size = strlen($string);
$columnIndex = 0;
$rowIndex = 0;
$fieldValue="";
$isEnclosured = false;
for($i=0; $i<$size;$i++) {
$char = $string{$i};
$addChar = "";
if($isEnclosured) {
if($char==$enclosureChar) {
if($i+1<$size && $string{$i+1}==$enclosureChar){
// escaped char
$addChar=$char;
$i++; // dont check next char
}else{
$isEnclosured = false;
}
}else {
$addChar=$char;
}
}else {
if($char==$enclosureChar) {
$isEnclosured = true;
}else {
if($char==$separatorChar) {
$array[$rowIndex][$columnIndex] = $fieldValue;
$fieldValue="";
$columnIndex++;
}elseif($char==$newlineChar) {
echo $char;
$array[$rowIndex][$columnIndex] = $fieldValue;
$fieldValue="";
$columnIndex=0;
$rowIndex++;
}else {
$addChar=$char;
}
}
}
if($addChar!=""){
$fieldValue.=$addChar;
}
}
if($fieldValue) { // save last field
$array[$rowIndex][$columnIndex] = $fieldValue;
}
return $array;
}
答案 2 :(得分:2)
问题是“\ n”转义字符串不会计算为Excel用于其行分隔符的相同换行符。 Excel使用的ASCII字符是ASCII 13.以下代码将有效地解析通过$ file_get_contents()方法传入的.csv文件。
<?php
//variable to store filename of file
$filename = $_SERVER['DOCUMENT_ROOT'] . "/site/docs/boothmap.csv";
//read file in as string
$file = file_get_contents($filename);
//convert csv to array
//first to single dimensional array
$array1D = explode(chr(13),$file);
//create new array to hold 2d array
$array2D = array();
//iterate through 1 dimensional array and explode each value to the new array
foreach($array1D as &$row)
{
array_push($array2D, explode(',',$row));
}
//pop off empty last row of array2D
array_pop($array2D);
//iterate through $array2D building table of data
//start table with column headers
echo "<table border=\"1\">\n<tr>\n<th>Company</th>\n<th>Booth #</th>\n<th>Location</th>\n</tr>\n";
foreach ($array2D as &$row)
{
echo "<tr>\n";
foreach($row as &$subrow)
{
echo "<td>" . $subrow . "</td>\n";
}
echo "</tr>\n";
}
//close table
echo "</table>";
答案 3 :(得分:0)
我最终能够使用某些特殊标志修改正则表达式以满足我的需要。我使用了以下函数调用:
preg_match_all('/"\d+",".*",".*"\n/sU', $csv_data, $matches);
这似乎有以下几个原因:
1)'s'标志告诉编辑抓住点下的换行符,通常情况并非如此。不幸的副作用是,合法的换行符也被点捕获,理论上可以将整个CSV与一个结果匹配,所以
2)我添加了U旗。这默认表示点不合适,因此,它目前只匹配一行。
答案 4 :(得分:0)
这是一个老线程,但我遇到了这个问题,我用正则表达式解决了它,所以你可以为此避免使用库。这里的代码是PHP,但它可以适应其他语言。
$parsedCSV = preg_replace('/(,|\n|^)"(?:([^\n"]*)\n([^\n"]*))*"/', '$1"$2 $3"', $parsedCSV);
如果内容太大,它可能效率不高,但它可以帮助很多情况并且可以重复使用这个想法,可以通过对较小的块进行优化来优化(但是你需要处理削减固定大小的缓冲区)。这个解决方案假设包含换行符的字段用双引号括起来,这似乎是一个有效的假设,至少对于我到目前为止看到的情况。此外,双引号应遵循,
或放在新行(或第一行)的开头。
示例:
field1,"field2-part1\nfield2-part2",field3
这里的\ n被空格替换,结果如下:
field1,"field2-part1 field2-part2",field3
正则表达式也应该处理多个换行符。
答案 5 :(得分:0)
我创建了这个PHP函数来将CSV解析成2D数组。它可以处理包含逗号,引号或换行符的数据。这比其他一些有效的解决方案运行得更快。
/**
* copyright 2018 Frank Forte
* Free for personal, non-commercial use
* contact me for inexpensive licenses to use and create derivative works
*/
protected static function parse_csv_forte (&$str, $delimiter = ",", $enclosure = '"', $escape = '"', $skip_empty_lines = true, $trim_fields = false)
{
// use linux line endings
$str = str_replace("\r\n","\n",$str);
$str = str_replace("\r","\n",$str);
// substitute line endings that are part of data
$num = strlen($str);
$quoted = false;
$last = null;
$escape = false;
for($i = 0; $i < $num; $i++)
{
if($str[$i] == $enclosure)
{
if($last == $enclosure)
{
$escape = !$escape;
if($escape)
{
$quoted = !$quoted;
}
}
else
{
if(!$escape)
{
$quoted = !$quoted;
}
}
}
if($str[$i] != $enclosure || $escape)
{
$escape = false;
}
if($quoted && $str[$i] == "\n")
{
$str[$i] = "\r";
}
$last = $str[$i];
}
if($skip_empty_lines)
{
$str = preg_replace("/\n+/","\n",$str);
$str = trim($str,"\n");
}
$str = explode("\n",$str);
$csv = [];
foreach($str as $e)
{
$e = str_getcsv($e, $delimiter, $enclosure, $escape);
foreach($e as $k => $f)
{
$e[$k] = str_replace("\r","\n",$f);
if($trim_fields)
{
$e[$k] = trim($e[$k]);
}
}
$csv[] = $e;
}
return $csv;
}
像这样使用:
$csv = parse_csv_forte(file_get_contents('file.csv'));
答案 6 :(得分:0)
这将起作用:https://github.com/synappnz/php-csv
include "csv.php";
$csv = new csv(file_get_contents("filename.csv"));
$rows = $csv->rows();
foreach ($rows as $row)
{
// do something with $row
}
答案 7 :(得分:0)
这是@Stephen答案的解决方法。它管理多行,并保留数组中的空单元格:
function csvstring_to_array(&$string, $CSV_SEPARATOR = ';', $CSV_ENCLOSURE = '"', $CSV_LINEBREAK = "\n") {
$o = array();
$cnt = strlen($string);
$esc = false;
$escesc = false;
$num = 0;
$i = 0;
$line = 0;
while ($i < $cnt) {
$s = $string[$i];
if ($s == $CSV_LINEBREAK) {
if ($esc) {
$o[$line][$num] .= $s;
} else {
$i++;
$line++;
$num = 0;
continue;
}
} elseif ($s == $CSV_SEPARATOR) {
if ($esc) {
$o[$line][$num] .= $s;
} else {
$num++;
$o[$line][$num] .= '';
$esc = false;
$escesc = false;
}
} elseif ($s == $CSV_ENCLOSURE) {
if ($escesc) {
$o[$line][$num] .= $CSV_ENCLOSURE;
$escesc = false;
}
if ($esc) {
$esc = false;
$escesc = true;
} else {
$esc = true;
$escesc = false;
}
} else {
if ($escesc) {
$o[$line][$num] .= $CSV_ENCLOSURE;
$escesc = false;
}
$o[$line][$num] .= $s;
}
$i++;
}
return $o;
}
答案 8 :(得分:0)
只需尝试一下,它就可以与PHP 7.4一起使用
function readCsv(string $filePath): array
{
$handle = fopen($filePath, 'r');
$row = fgetcsv($handle);
$rows = [];
while (!empty($row)) {
$rows[] = $row;
$row = fgetcsv($handle);
}
return $rows;
}
答案 9 :(得分:-2)