Php 如何解析数据中包含换行符的excel CSV数据?

Php 如何解析数据中包含换行符的excel CSV数据?,php,excel,parsing,csv,line-breaks,Php,Excel,Parsing,Csv,Line Breaks,我试图用PHP解析一组CSV数据,但有一个主要问题。其中一个字段是长描述字段,该字段本身包含机柜中的换行符 我的主要问题是编写一段代码,它可以逐行分割数据,但也可以识别数据中何时不应使用换行符。此字段中的换行符未正确转义,因此很难将其与合法换行符区分开来 我试图找到一个能够正确处理它的正则表达式,但到目前为止运气不佳。有什么想法吗 CSV格式: "####","text data here", "text data \n with linebreaks \n here"\n "####","mo

我试图用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

根据PHP函数文档中的注释者aleske的说法:

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; 
} 
函数csvstring_to_数组(&$string,$CSV_分隔符=';',$CSV_ENCLOSURE='”,$CSV_LINEBREAK=“\n”){
$o=数组();
$cnt=strlen($string);
$esc=假;
$scc=假;
$num=0;
$i=0;
而($i<$cnt){
$s=$string[$i];
如果($s==$CSV_LINEBREAK){
如果($esc){
$o[$num]。=$s;
}否则{
$i++;
打破
} 
}elseif($s==$CSV_分隔符){
如果($esc){
$o[$num]。=$s;
}否则{
$num++;
$esc=假;
$scc=假;
} 
}elseif($s==$CSV_附件){
如果($sc){
$o[$num]。=$CSV\u机柜;
$scc=假;
} 
如果($esc){
$esc=假;
$escsc=真;
}否则{
$esc=真;
$scc=假;
} 
}否则{
如果($sc){
$o[$num]。=$CSV\u机柜;
$scc=假;
} 
$o[$num]。=$s;
} 
$i++;
} 
//$string=substr($string,$i);
退还$o;
} 

这看起来很有用。

您可以使用或解析csv。查看php文档中的示例。

我最终能够修改带有特定标志的正则表达式以满足我的需要。我使用了以下函数调用:

preg_match_all('/"\d+",".*",".*"\n/sU', $csv_data, $matches);
这似乎有几个原因:

1) “s”标志告诉编辑器捕捉点下的换行符,通常情况下不是这样。不幸的是,合法的换行符也被点捕捉,理论上可以将整个CSV匹配到一个结果,因此

2) 我添加了U标志。这会告诉点在默认情况下是取消冻结的,因此,它当前只匹配一条线。

问题是“\n“转义字符串的计算结果与Excel用作行分隔符的新行字符不同。Excel使用的ASCII字符是ASCII 13。下面的代码将有效地解析通过$file\u get\u contents()方法传入的.csv文件


我发现在将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;
}
函数csvstring_to_数组($string,$separatorChar=',',$enclosureChar=',$newlineChar=PHP_EOL){
//@作者:Klemen Nagode
$string=dos2unix($string);
$array=array();
$size=strlen($string);
$columnIndex=0;
$ROWDINDEX=0;
$fieldValue=“”;
$isEnclosured=false;

对于($i=0;$i这是一个旧线程,但我遇到了这个问题,我用正则表达式解决了这个问题,这样你就可以避免使用库了。这里的代码是用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


正则表达式也应该处理多个换行符。

我创建这个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;
}
这将有助于:


这是对@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;
}
函数csvstring_to_数组(&$string,$CSV_分隔符=';',$CSV_ENCLOSURE='”,$CSV_LINEBREAK=“\n”){
$o=数组();
$cnt=strlen($string);
$esc=假;
$scc=假;
$num=0;
$i=0;
$line=0;
而($i<$cnt){
$s=$string[$i];
如果($s==$CSV\u换行符){
如果($esc){
$o[$line][$num]。=$s;
}否则{
$i++;
$line++;
$num=0;
继续;
}
}elseif($s==$CSV\u分隔符){
如果($esc){
$o[$line][$num]。=$s;
}否则{
$num++;
$o[$line][$num]。='';
$esc=假;
$scc=假;
}
}elseif($s==$CSV\U附件){
若有($sc){
$o[$line][$num]。=$CSV\u机柜;
$scc=假;
}
如果($esc){
$esc=假;
$escsc=真;
}否则{
$esc=真;
$scc=假;
}
$csv = parse_csv_forte(file_get_contents('file.csv'));
include "csv.php";
$csv = new csv(file_get_contents("filename.csv"));
$rows = $csv->rows();
foreach ($rows as $row)
{
  // do something with $row
}
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;
}