【问题标题】:How can you parse excel CSV data that contains linebreaks in the data?如何解析数据中包含换行符的 excel CSV 数据?
【发布时间】:2011-03-17 17:48:40
【问题描述】:

我正在尝试使用 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 excel parsing csv line-breaks


    【解决方案1】:

    刚刚尝试过,它适用于 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;
        }
    

    【讨论】:

      【解决方案2】:

      这是对@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;
      }
      

      【讨论】:

        【解决方案3】:

        这将起作用: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
        }
        

        【讨论】:

          【解决方案4】:

          我创建了这个 PHP 函数来将 CSV 解析为二维数组。它可以处理包含逗号、引号或换行符的数据。这比其他一些可行的解决方案运行得更快。

          /**
           * 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'));
          

          【讨论】:

            【解决方案5】:

            这是一个旧线程,但我遇到了这个问题,我用一个正则表达式解决了它,所以你可以避免为此使用一个库。这里的代码是 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

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

            【讨论】:

              【解决方案6】:

              我发现在将 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;
              }
              

              【讨论】:

                【解决方案7】:

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

                preg_match_all('/"\d+",".*",".*"\n/sU', $csv_data, $matches);
                

                这似乎有几个原因:

                1) 's' 标志告诉编辑器在点下捕捉换行符,通常情况并非如此。不幸的副作用是合法的换行符也会被点捕获,理论上可以将整个 CSV 匹配到一个结果,所以

                2) 我添加了 U 标志。这告诉点在默认情况下是不贪婪的,因此,它目前只匹配一行。

                【讨论】:

                  【解决方案8】:

                  问题在于“\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>";
                  

                  【讨论】:

                    【解决方案9】:

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

                    【讨论】:

                    • 几年前我最后一次尝试使用它们时,getcsv 函数都不会接受引用字段中的换行符。他们会认为这是记录的结束。
                    【解决方案10】:

                    根据 PHP 的 fgetcsv 函数文档中的评论者 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; 
                    } 
                    

                    看起来它可以解决问题。

                    【讨论】:

                    • fgetcsv($file)。在 2019 年 php 7 中工作正常
                    猜你喜欢
                    • 2013-08-31
                    • 2018-07-24
                    • 2022-08-23
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-06-19
                    • 1970-01-01
                    • 2013-06-01
                    相关资源
                    最近更新 更多