如何解析包含数据中的换行符的Excel数据?

时间:2010-07-19 03:58:06

标签: php excel parsing csv line-breaks

我正在尝试使用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

10 个答案:

答案 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)

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