Как вы можете анализировать данные Excel CSV, содержащие разрывы строк в данных? - PullRequest
6 голосов
/ 19 июля 2010

Я пытаюсь проанализировать набор данных CSV, используя PHP, но у меня есть серьезная проблема. Одним из полей является поле длинного описания, которое само содержит разрывы строк внутри корпусов.

Моя основная проблема - написание фрагмента кода, который может разбивать данные по строкам, но также распознавать, когда не следует использовать разрывы строк в данных. Разрывы строк в этом поле не могут быть должным образом экранированы, что делает их трудно отличимыми от законных разрывов строк.

Я пытался придумать регулярное выражение, которое может правильно с ним справиться, но пока не повезло. Есть идеи?

CSV формат:

"####","text data here", "text data \n with linebreaks \n here"\n
"####","more text data", "more data \n with \n linebreaks \n here"\n

Ответы [ 9 ]

10 голосов
/ 19 июля 2010

По словам aleske, комментатор в документации по функции PHP fgetcsv :

Средства обработки CSV в PHP нестандартны и противоречат 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; 
} 

Похоже, это будетсделать трюк.

4 голосов
/ 19 марта 2013

Я обнаружил, что вы можете использовать обычный CSV-анализатор после преобразования CSV-файла в формат Unix.

Вот функция, которая помогла мне.

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 голосов
/ 28 декабря 2011

Проблема в том, что escape-строка "\ n" не соответствует тому же символу новой строки, который Excel использует для своего разделителя строк.Символ ASCII, который используется в Excel, - это ASCII 13. Следующий код будет эффективно анализировать файл .csv, переданный с помощью метода $ file_get_contents ().

<?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>";
0 голосов
/ 28 января 2019

Это исправление для ответа @ 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;
}
0 голосов
/ 04 октября 2018

Это будет работать: 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
}
0 голосов
/ 29 мая 2018

Я создал эту функцию 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'));
0 голосов
/ 20 октября 2016

Это старый поток, но я столкнулся с этой проблемой, и я решил ее с помощью регулярного выражения, чтобы вы могли избежать библиотеки только для этого.Здесь код написан на 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

Регулярное выражение также должно обрабатывать несколько разрывов строк.

0 голосов
/ 19 июля 2010

В итоге я смог изменить регулярное выражение с определенными специальными флагами, чтобы оно соответствовало моим потребностям.Я использовал следующий вызов функции:

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

Кажется, это работает по нескольким причинам:

1) Флаг 's' говорит редактору перехватывать символы новой строки под точкой, что обычноне тот случайК сожалению, побочным эффектом этого является то, что точные символы новой строки также попадают в точку, которая теоретически может сопоставить весь CSV с одним результатом, поэтому

2) Я добавил флаг U.Это говорит о том, что по умолчанию точка не является жадной, и в настоящее время она соответствует только одной строке на фрагмент.

0 голосов
/ 19 июля 2010

Вы можете использовать fgetcsv или strgetcsv для анализа CSV. Посмотрите примеры внутри документации php.

...