Даны строки вида:
aa1 bbb ccc
aa2 "bbb ccc" ddd
aa3 "b c d" "s d f" ddd
"aaa4" ccc
Нужно написать регулярное выражение, которое разобъет эти строки либо по пробелу, либо по кавычкам. То есть, если элемент без кавычек — нужно брать его. А если в кавычках — то считать содержимое кавычек одним элементом.
На выходе должно получиться:
{'aa1', 'bbb', 'ccc'},
{'aa2', 'bbb ccc', 'ddd'},
{'aa3', 'b c d', 's d f', 'ddd'},
{'aaa4', 'ccc'}
Это похоже на CSV.
Тестовая программа у нас начинается как-то так:
my @text = ('aa1 bbb ccc', 'aa2 "bbb ccc" ddd', 'aa3 "b c d" "s d f" ddd', '"aaa4" ccc');
foreach my $text (@text) {
#парсим текст и выводим для контроля
}
Мне в голову пришло сразу несколько вариантов решения этой задачи.
Первое, о чем я подумал - прогон через регулярку несколько раз. Сперва вытаскиваем элементы в кавычках и оставляем на их месте плейсхолдеры с номерами. Потом спокойно сплитим строки. Затем заменяем плейсхолдеры соответствующими им элементами.
Код будет выглядеть как-то так:
#функция, которая заменяет закавыченный элемент на плейсхолдер
sub replace_quoted {
my $elm = shift;
my $holders = shift;
$elm =~ s/"//g; #удаляем кавычки
push @$holders, $elm; #запоминаем
return "___".$#{$holders}."___"; #возвращаем холдер вида ___число___
}
#функция, которую мы дергаем, чтобы получить итемы
sub get_items {
my $str = shift;
my @holders;
#эта регулярка находит закавыченные элементы и заменяет их на результат работы функции
$str =~ s/(".+?")/replace_quoted($1, \@holders)/eg;
#здесь можно вывести $str для понимания что происходит
#сплитим
my @arr = split /\s+/, $str;
#пробегаем по элементам
foreach my $elm (@arr) {
#если это плейсхолдер - заменяем его
if($elm =~ /^___(\d+)___$/) {
$elm = $holders[$1];
}
}
#готово
return @arr;
}
Это решение простое и надежное. Оно не раз доказывало свою надежность и эффективность. К преимуществам относится простота регулярных выражений. То есть, трудно ошибиться при парсинге. Надежность выше. Но зато есть две довольно громоздких функции. Кроме того, приходится запоминать какие-то неизвестного размера массивы данных в @holders.
Как бы то ни было - второе решение было похоже на это. Оно заключалось в том, чтобы просто рассплитить строку по пробелу, а потом накапливать элементы в зависимости от того, начинается ли он с кавычки. Если да - то элементом считать все последующие, включая тот, который заканчивается кавычкой. Только потом начинать следующий элемент.
Но этот вариант слишком уж "в лоб" и как-то мне не очень нравится. К тому же, интересно написать именно регулярное выражение, решающее такую задачу. И я написал его.
my @arr = $text =~ /(("?)(.+?)\2(?: |$))/g;
Регулярка работает просто. Сначала она ищет кавычку или пустоту. Потом - сам элемент. Если была кавычка, то элемент кончается на ней. Если нет - кончается пробелом или концом строки.
Регулярка работает, но выдает избыточные данные. Придется потом фильтровать результирующий массив - нам нужен только каждый третий элемент. Кроме того, опыт работы с регулярными выражениями подсказывает, что сложные регулярки склонны к неожиданным сбоям. Не в силу программных ошибок, а в силу криворукости мастера, написавшего таковую. Мои тесты это регулярное выражение прошло. Но возможно есть такая строка, на которой оно споткнется. Надежность ниже, чем у вышеприведенного метода. Но зато как элегантно!:)
Надеюсь, эти кусочки кода достаточно просты чтобы мне не нужно было переводить их на PHP, ведь в первом случае главное - идея, а во втором - регулярное выражение. Думаю, читатель без труда сможет адаптировать это решение к своим задачам.
А если у вас не получается - можете обратиться ко мне за помощью. Если у вас простой вопрос - денег не попрошу, обращайтесь.