Php 如何解析数据中包含换行符的excel CSV数据?
我试图用PHP解析一组CSV数据,但有一个主要问题。其中一个字段是长描述字段,该字段本身包含机柜中的换行符 我的主要问题是编写一段代码,它可以逐行分割数据,但也可以识别数据中何时不应使用换行符。此字段中的换行符未正确转义,因此很难将其与合法换行符区分开来 我试图找到一个能够正确处理它的正则表达式,但到目前为止运气不佳。有什么想法吗 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
"####","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;
}