Как я могу разобрать CSV в Perl с регулярным выражением?

У меня возникли проблемы с анализом данных CSV с кавычками. Моя основная проблема заключается в кавычках внутри поля. В следующем примере строки 1 - 4 работают правильно, но 5,6 и 7 не работают.

COLLOQ_TYPE,COLLOQ_NAME,COLLOQ_CODE,XDATA
S,"BELT,FAN",003541547,
S,"BELT V,FAN",000324244,
S,SHROUD SPRING SCREW,000868265,
S,"D" REL VALVE ASSY,000771881,
S,"YBELT,"V"",000323030,
S,"YBELT,'V'",000322933,

Я бы хотел избежать Text:: CSV, поскольку он не установлен на целевом сервере. Понимая, что CSV сложнее, чем они выглядят, я использую рецепт из Cookbook Perl.

sub parse_csv {
  my $text = shift; #record containg CSVs
  my @columns = ();
  push(@columns ,$+) while $text =~ m{
    # The first part groups the phrase inside quotes
    "([^\"\\]*(?:\\.[^\"\\]*)*)",?
      | ([^,]+),?
      | ,
    }gx;
  push(@columns ,undef) if substr($text, -1,1) eq ',';
  return @columns ; # list of vars that was comma separated.
}

Есть ли у кого-нибудь предложение по улучшению регулярного выражения для обработки вышеуказанных случаев?

Ответ 1

Пожалуйста, попробуйте использовать CPAN

Нет причин, по которым вы не могли загрузить копию Text::CSV или любую другую реализацию на основе CSV парсера CSV и установите его в свой локальный каталог или в каталог lib/sub вашего проекта, чтобы он был установлен вместе с развертыванием ваших проектов.

Если вы не можете хранить текстовые файлы в своем проекте, то мне интересно, как именно вы кодируете свой проект.

http://novosial.org/perl/life-with-cpan/non-root/

Должен быть хороший справочник о том, как локализовать их в рабочем состоянии.

Не использовать CPAN - это действительно рецепт катастрофы.

Пожалуйста, подумайте об этом, прежде чем пытаться написать свою собственную реализацию CSV.

Text::CSV - это более сотни строк кода, включая исправленные ошибки и кромки, и повторная запись этого с нуля будет просто сделать вы узнаете, насколько ужасный CSV может быть трудным.

note: Я усвоил этот трудный путь. Получил мне полный день, чтобы получить рабочий синтаксический анализатор CSV в PHP, прежде чем я обнаружил, что встроенный в него был добавлен в более позднюю версию. Это действительно что-то ужасное.

Ответ 2

Вы можете разобрать CSV, используя Text:: ParseWords, который поставляется с Perl.

use Text::ParseWords;

while (<DATA>) {
    chomp;
    my @f = quotewords ',', 0, $_;
    say join ":" => @f;
}

__DATA__
COLLOQ_TYPE,COLLOQ_NAME,COLLOQ_CODE,XDATA
S,"BELT,FAN",003541547,
S,"BELT V,FAN",000324244,
S,SHROUD SPRING SCREW,000868265,
S,"D" REL VALVE ASSY,000771881,
S,"YBELT,"V"",000323030,
S,"YBELT,'V'",000322933,

который правильно анализирует ваш CSV....

# => COLLOQ_TYPE:COLLOQ_NAME:COLLOQ_CODE:XDATA
# => S:BELT,FAN:003541547:
# => S:BELT V,FAN:000324244:
# => S:SHROUD SPRING SCREW:000868265:
# => S:D REL VALVE ASSY:000771881:
# => S:YBELT,V:000323030:
# => S:YBELT,'V':000322933:

Единственная проблема, с которой я столкнулся с Text:: ParseWords, - это когда скрытые кавычки в данных не сбрасываются правильно. Однако это плохо построенные CSV-данные и вызовут проблемы с большинством парсеров CSV; -)

Итак, вы можете заметить, что

# S,"YBELT,"V"",000323030,

вышло как (т.е. кавычки опустились вокруг "V" )

# S:YBELT,V:000323030:

однако, если он так убежал

# S,"YBELT,\"V\"",000323030,

то кавычки будут сохранены

# S:YBELT,"V":000323030:

Ответ 3

Это работает как charm

Строка

считается разделенной запятой с вложенным,

my @columns = Text::ParseWords::parse_line(',', 0, $line);

Ответ 4

протестирован; работает: -

$_.=','; # fake an ending delimiter

while($_=~/"((?:""|[^"])*)",|([^,]*),/g) {
  $cell=defined($1) ? $1:$2; $cell=~s/""/"/g; 
  print "$cell\n";
}

# The regexp strategy is as follows:
# First - we attempt a match on any quoted part starting the CSV line:-
#  "((?:""|[^"])*)",
# It must start with a quote, and end with a quote followed by a comma, and is allowed to contain either doublequotes - "" - or anything except a sinlge quote [^"] - this goes into $1
# If we can't match that, we accept anything up to the next comma instead, & put it into $2
# Lastly, we convert "" to " and print out the cell.

следует предупредить, что файлы CSV могут содержать ячейки со встроенными символами новой строки внутри кавычек, поэтому вам нужно будет это сделать, если вы будете считывать данные в строке по времени:

if("$pre$_"=~/,"[^,]*\z/) {
  $pre.=$_; next;
}
$_="$pre$_";

Ответ 5

Поиск совпадающих пар с использованием регулярных выражений является нетривиальной и обычно неразрешимой задачей. Существует множество примеров в книге Jeffrey Friedl Освоение регулярных выражений. Сейчас у меня его нет, но я помню, что он использовал CSV для некоторых примеров.

Ответ 6

Вы можете (попытаться) использовать CPAN.pm, чтобы просто установить или обновить вашу программу Text:: CSV. Как уже было сказано, вы можете даже "установить" его в домашний или локальный каталог и добавить этот каталог в @INC (или, если вы предпочитаете не использовать блоки BEGIN, вы можете use lib 'dir'; - это, вероятно, лучше).

Ответ 7

Испытано:


use Test::More tests => 2;

use strict;

sub splitCommaNotQuote {
    my ( $line ) = @_;

    my @fields = ();

    while ( $line =~ m/((\")([^\"]*)\"|[^,]*)(,|$)/g ) {
        if ( $2 ) {
            push( @fields, $3 );
        } else {
            push( @fields, $1 );
        }
        last if ( ! $4 );
    }

    return( @fields );
}

is_deeply(
    +[splitCommaNotQuote('S,"D" REL VALVE ASSY,000771881,')],
    +['S', '"D" REL VALVE ASSY', '000771881', ''],
    "Quote in value"
);
is_deeply(
    +[splitCommaNotQuote('S,"BELT V,FAN",000324244,')],
    +['S', 'BELT V,FAN', '000324244', ''],
    "Strip quotes from entire value"
);