Парсер Parsec csv анализирует дополнительную строку

Я определил следующий синтаксический анализатор Parsec для анализа файлов csv в таблицу строк, т.е. [[String]]

--A csv parser is some rows seperated, and possibly ended, by a newline charater
csvParser = sepEndBy row (char '\n')
--A row is some cells seperated by a comma character
row = sepBy cell (char ',')
--A cell is either a quoted cell, or a normal cell
cell = qcell <|> ncell
--A normal cell is a series of charaters which are neither , or newline. It might also be an escape character
ncell = many (escChar <|> noneOf ",\n")
--A quoted cell is a " followd by some characters which either are escape charaters or normal characters except for "
qcell = do
    char '"'
    res <- many (escChar <|> noneOf "\"")
    char '"'
    return res
--An escape character is anything followed by a \. The \ will be discarded.
escChar = char '\\' >> anyChar

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

Работает очень хорошо, но есть проблема. Он создает дополнительную пустую строку в таблице. Итак, если у меня, например, есть файл csv с 10 строками (то есть только 10 строк. В конце нет пустых строк*), структура [[String]] будет иметь длину 11, а последний список Strings будет содержать 1 элемент. Пустой String (по крайней мере, так он выглядит при печати с помощью show).

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

Еще одна вещь, которую я заметил, заключается в том, что если после данных в файлах csv есть пустые строки, они закончатся строками, содержащими только пустые String в таблице. Я думал, что использование sepEndBy вместо sepBy приведет к игнорированию лишних пустых строк. Разве это не так?

* После просмотра текстового файла в шестнадцатеричном редакторе кажется, что он действительно заканчивается символом новой строки, хотя vim этого не показывает...


person Andreas Vinter-Hviid    schedule 23.07.2012    source источник


Ответы (1)


Если вы хотите, чтобы в каждой строке была хотя бы одна ячейка, вы можете использовать sepBy1 вместо sepBy. Это также должно остановить анализ пустых строк как строки. Разница между sepBy и sepBy1 такая же, как разница между many и many1: версия 1 анализирует только последовательности хотя бы одного элемента. Итак, row становится таким:

row = sepBy1 cell (char ',')

Кроме того, обычно используется sepBy1 в инфиксе: cell `sepBy1` char ','. Это читается более естественно: у вас есть «ячейка, разделенная запятой», а не «ячейка, разделенная запятой».

РЕДАКТИРОВАТЬ: если вы не хотите принимать пустые ячейки, вы должны указать, что ncell имеет хотя бы один символ, используя many1:

ncell = many1 (escChar <|> noneOf ",\n")
person Tikhon Jelvis    schedule 23.07.2012
comment
Мне жаль. Я не совсем корректно изложил свою проблему. Оказывается, лишняя строка и пустые строки строк на самом деле не становятся пустыми списками при разборе. Вместо этого они содержат 1 элемент, который выглядит как пустой String. Я изменю свой вопрос, чтобы отразить это. Ваше решение в любом случае не работает. Возможно потому, что я не правильно сформулировал задачу. Но я все равно должен использовать sepBy1, так что спасибо за помощь :) - person Andreas Vinter-Hviid; 24.07.2012
comment
@andvin Проблема в том, что many и sepBy могут быть успешными без потребления, а завершающее sep необязательно с sepEndBy. Когда достигается последнее '\n', синтаксический анализатор пытается прочитать следующую строку с оставшимися входными данными. row пытается проанализировать первую ячейку. ncell завершается успешно с "", строка ищет ',', ничего не находит, завершается успешно с одной ячейкой [""]. rows проверяет наличие '\n', ничего не находит и завершает работу с последней строкой [""]. Вы должны сделать так, чтобы cell и row терпели неудачу при пустом вводе, чтобы избавиться от последней пустой строки ([], если вы используете many1 в ncell, но не sepBy1 в строке). - person Daniel Fischer; 24.07.2012
comment
Спасибо! Это решает проблему. Однако теперь у меня не может быть пустых ячеек, но я вижу, как невозможно различить пустые строки и строки только с одной пустой ячейкой. Но, может быть, можно различать строки с одной пустой ячейкой и той последней несуществующей строкой? Может быть, если бы я использовал sepBy вместо sepEndBy? Я имею в виду, что должна быть возможность определить разницу между пустой строкой и EOF, верно? В любом случае, я думаю, мне следует изучить, как обычно определяется csv, и допускает ли он даже строки разной длины. - person Andreas Vinter-Hviid; 25.07.2012
comment
@andvin: Есть ли в вашем вводе что-нибудь (например, пробелы) в последней строке? Есть ли вообще завершающая новая строка после последней реальной строки CSV? - person Tikhon Jelvis; 25.07.2012
comment
На самом деле после последней строки csv есть новая строка, но я думал, что ее нет. Кажется, что vim добавляет его автоматически, а затем скрывает. Итак, теперь я в основном понимаю, почему мой код делает то, что он делает. Мне просто нужно выяснить, как обрабатывать пустые строки. - person Andreas Vinter-Hviid; 25.07.2012