Перейти к содержимому






Фотография
* * * * * 1 голосов

Парсер CSV

Написано Fingercomp , в Парсеры, Tutorials 01 Ноябрь 2016 · 238 просмотров

algorithms python lua library csv parser

CSV идёт от Comma-Separated Values, что, в общем, довольно точно описывает этот формат хранения таблиц. Вот типичная таблица:

aaa,bbb,ccc,ddd
eee,fff,ggg,hhh
Как видно, строки отделяются \n, а ячейки ­— запятой. Последняя строка может иметь или не иметь \n.

 

Формат очень простой. Описывается он в RFC 4180. Там всего 7 пунктов. Ну а раз простой, давайте соорудим парсер.

 

Вот у нас есть строка aaa,bbb,ccc,ddd\neee,fff,ggg,hhh. Задача: сделать из неё

[ [ "aaa", "bbb", "ccc", "ddd" ]
, [ "eee", "fff", "ggg", "hhh" ]
]

 

Так как позже я немного усложню парсер, очевидный вариант со split, которая делит строку, опустим. Сделаем так:

def parse_csv(s):
    # Сюда идёт результат
    result = []

    # Текущая строка
    row = []

    # Текущая ячейка
    cell = ""

    # Проходимся по строке
    for i in range(len(s)):
	    # Текущий символ
	    c = s[i]

	    if c == ",":
		    # Если символ — запятая, закрываем ячейку
		    row.append(cell)
		    cell = ""
	    elif c == "\n":
		    # Если это перевод строки, то закрываем ячейку и строку
		    row.append(cell)
		    cell = ""
		    result.append(row)
		    row = []
	    else:
		    # Любой другой символ добавляем в ячейку
		    cell += c

    # Возвращаем результат
    return result
Запускаем:
>>> parse_csv("aaa,bbb,ccc,ddd\neee,fff,ggg,hhh\n")
[['aaa', 'bbb', 'ccc', 'ddd'], ['eee', 'fff', 'ggg', 'hhh']]
 
>>> parse_csv("aaa,bbb,ccc,ddd\neee,fff,ggg,hhh")
[['aaa', 'bbb', 'ccc', 'ddd']]

 

Действительно, в конце может и не быть \n. Давайте поправим:

def parse_csv(s):
    result = []
    row = []
    cell = ""

    for i in range(len(s)):
	    c = s[i]
	    if c == ",":
		    row.append(cell)
		    cell = ""
	    elif c == "\n":
		    row.append(cell)
		    cell = ""
		    result.append(row)
		    row = []
	    else:
		    cell += c

    # Если ячейка не пуста
    if cell:
	    # Закрываем ячейку и строку
	    row.append(cell)
	    result.append(row)

    return result
Проверяем:
>>> parse_csv("aaa,bbb,ccc,ddd\neee,fff,ggg,hhh\n")
[['aaa', 'bbb', 'ccc', 'ddd'], ['eee', 'fff', 'ggg', 'hhh']]
 
>>> parse_csv("aaa,bbb,ccc,ddd\neee,fff,ggg,hhh")
[['aaa', 'bbb', 'ccc', 'ddd'], ['eee', 'fff', 'ggg', 'hhh']]
Замечательно.

 

Почему я проверяю только ячейку, а не строку ещё? Просто пустая ячейка и непустая строка может быть только тогда, когда на конце строки висит запятая. aaa,bbb,. А это явно запрещено по RFC.

 

В текущем виде в ячейке у нас не получится хранить \n и ,. Если первый символ ещё кое-как, то без запятой как-то совсем не весело, верно?
На наше счастье, в спецификации есть и это. Ячейку можно поместить в двойные кавычки (", кто не понял), тогда до следующей кавычки обрабатываться \n и , не будут.

 

Давайте улучшим наш парсер, добавив поддержку этих самых кавычек. Так как у нас посимвольный парсинг, сделать это гораздо проще. Вот так:

def parse_csv(s):
    result = []
    row = []
    cell = ""

    # Начиналась ли текущая ячейка с кавычки
    quoted = False

    for i in range(len(s)):
	    c = s[i]
	    if quoted:
		    if c == '"':
			    # Закрывающая кавычка
			    quoted = False
		    else:
			    cell += c
	    else:
		    if c == '"':
			    if not cell:
				    # Открывающая кавычка в начале ячейки
				    quoted = True
			    else:
				    # Кавычка в середине строки: запрещено
				    return False
		    elif c == ",":
			    row.append(cell)
			    cell = ""
		    elif c == "\n":
			    row.append(cell)
			    cell = ""
			    result.append(row)
			    row = []
		    else:
			    cell += c

    if cell:
	    if quoted:
		    # Где-то не закрыли кавычки
		    return False
	    row.append(cell)
	    result.append(row)

    return result

 

Проверяем:

Спойлер

 

Всё верно, кроме последнего. В середине строки в закавыченных строках эти самые кавычки должны быть экранированы вот так: "". Например: "aaa""bbb,ccc",ddd,eee. Давайте починим и это.

def parse_csv(s):
    result = []
    row = []
    cell = ""
    quoted = False

    # Является ли предыдущий символ кавычкой
    prevQuote = False

    for i in range(len(s)):
	    c = s[i]
	    if quoted:
		    if c == '"':
			    # Помечаем, что у нас есть кавычка в середине строки.
			    # Она может быть экранированной.
			    prevQuote = True
			    quoted = False
		    else:
			    cell += c
	    else:
		    if c == '"':
			    if not cell:
				    quoted = True
			    else:
				    if prevQuote:
					    # Если у нас прошлый символ был кавычкой,
					    # то получаем экранированную кавычку.
					    cell += '"'
					    quoted = True
					    prevQuote = False
				    else:
					    return False
		    elif c == ",":
			    row.append(cell)
			    cell = ""

			    # Кавычка была закрывающей
			    prevQuote = False
		    elif c == "\n":
			    row.append(cell)
			    cell = ""
			    result.append(row)
			    row = []

			    # Кавычка была закрывающей
			    prevQuote = False
		    else:
			    if prevQuote:
				    # Мы ждали кавычку или закрытие ячейки.
				    return False
			    cell += c

    if cell:
	    if quoted:
		    return False
	    row.append(cell)
	    result.append(row)

    return result

 

Опять тестируем:

Спойлер

 

Вот и всё. 44 строки кода на Python — и мы можем парсить CSV.
Я также переписал парсер на Lua, опубликовал его в OPPM под libcsv. Можете качать и радоваться. Вот сырцы.

 

Ну и надеюсь, это было менее сложно, чем мои записи про пакетные менеджеры до этого, и вы смогли прочитать это :rolleyes:.



  • Totoro и Sharplook это нравится



Я также переписал парсер на Lua, опубликовал его в OPPM под libcsv

 

Буржуй что ли? Публикуй в нашей репе! :P

Форму добавления сначала сделай.

Форму добавления сначала сделай.

 

А кто у нас джедай консолей? :P


Обратные ссылки на эту запись [ URL обратной ссылки ]

Обратных ссылок на эту запись нет

Последние версии

Стабильная: 1.6.1+hotfix.1
Последняя: 1.6.1+hotfix.1

Мои программы

Автокрафт на OpenComputers и AE

http://computercraft...-opencomputers/

 

Минималистичные и красивые часы

http://computercraft...0-finger-clock/

 

Чат на GoogolGlasses

http://computercraft...-googolglasses/

 

Лампомат

http://computercraft...-oc-lamp-o-mat/

 

Автокрафт 2: без АЕ

http://computercraft...2-teper-bez-ae/

 

Клиент Гиста

http://computercraft...-opencomputers/

 

Управление нанитами

http://computercraft...nanitami-snova/

 

Чат на OpenPeripheral а-ля IRC

http://computercraft.ru/topic/1517-

Искать в моем блоге

Новые записи

Новые комментарии

Январь 2017

В П В С Ч П С
1234567
891011121314
1516 17 18192021
22232425262728
293031    

Полезные ссылки

Официальный сайт OpenComputers

http://oc.cil.li/

 

Официальный форум OpenComputers

http://oc.cil.li/index.php?/index

 

Репозиторий OpenComputers на ГитХабе

https://github.com/M...s/OpenComputers

 

Последние релизы OpenComputers

https://github.com/M...puters/releases

 

Jenkins-сервер

http://ci.cil.li/job...s-dev-MC1.7.10/
 

Репозиторий OpenPrograms

https://github.com/OpenPrograms

 

Репозиторий моих программ

https://github.com/O...rcomp-Programs/

0 посетителей

0 пользователей, 0 гостей, 0 анонимных

Последние посетители

  • Фотография
    Alex
    Вчера, 02:53
  • Фотография
    vx13
    15 янв 2017 - 20:09
  • Фотография
    Stanislavich
    14 янв 2017 - 23:24
  • Фотография
    Xytabich
    13 янв 2017 - 14:30
  • Фотография
    Litvinov
    13 янв 2017 - 03:24
  • Фотография
    ivan52945
    12 янв 2017 - 23:11
  • Фотография
    electronic_steve
    12 янв 2017 - 21:11
  • Фотография
    Stirn
    12 янв 2017 - 19:48
  • Фотография
    Programist135
    12 янв 2017 - 18:44
  • Фотография
    MeXaN1cK
    12 янв 2017 - 18:29
  • Фотография
    _Star_Craft_
    12 янв 2017 - 17:53
  • Фотография
    Saintmare
    11 янв 2017 - 08:26
  • Фотография
    Asior
    11 янв 2017 - 07:45
  • Фотография
    Fedel
    11 янв 2017 - 03:30
  • Фотография
    Cleveron
    10 янв 2017 - 20:28
  • Фотография
    slavik95_ua
    10 янв 2017 - 01:28
  • Фотография
    LeshaInc
    08 янв 2017 - 14:52
  • Фотография
    ALeXeR
    08 янв 2017 - 11:00
  • Фотография
    cyber01
    07 янв 2017 - 23:16
  • Фотография
    POMAH3
    07 янв 2017 - 19:44
  • Фотография
    incvIZ
    07 янв 2017 - 17:31
  • Фотография
    Totoro
    07 янв 2017 - 15:48
  • Фотография
    qwertyMAN
    07 янв 2017 - 12:52
  • Фотография
    Sharplook
    07 янв 2017 - 00:10
  • Фотография
    SDV
    05 янв 2017 - 21:39
  • Фотография
    cloud
    05 янв 2017 - 14:16
  • Фотография
    mrlobaker
    04 янв 2017 - 16:19
  • Фотография
    Kartograf
    04 янв 2017 - 13:43
  • Фотография
    DimaZCOM
    03 янв 2017 - 21:29
  • Фотография
    mrGreen
    02 янв 2017 - 18:43
  • Фотография
    Nikitat
    02 янв 2017 - 14:51
  • Фотография
    Zabqer
    02 янв 2017 - 12:17
  • Фотография
    prostoshu
    02 янв 2017 - 09:32
  • Фотография
    Vait
    01 янв 2017 - 13:17
  • Фотография
    YuRaNnNzZZ
    30 дек 2016 - 20:58
  • Фотография
    Kid
    29 дек 2016 - 15:38
  • 26 дек 2016 - 11:07
  • Фотография
    LuaGamer
    26 дек 2016 - 08:23
  • Фотография
    Misha123
    25 дек 2016 - 17:23
  • Фотография
    DarthWirthe
    25 дек 2016 - 16:09
  • Фотография
    Quant
    24 дек 2016 - 11:42
  • Фотография
    NEO
    23 дек 2016 - 20:29
  • Фотография
    VankaPapanka
    23 дек 2016 - 12:26
  • Фотография
    GeorgSorok16
    23 дек 2016 - 10:33
  • Фотография
    HalfLolek
    21 дек 2016 - 14:21
  • Фотография
    i_ivanov10
    21 дек 2016 - 08:26
  • Фотография
    lLuffy
    21 дек 2016 - 03:42
  • Фотография
    SGMack
    19 дек 2016 - 17:45
  • Фотография
    Arseniy10
    19 дек 2016 - 16:14
  • Фотография
    newbie
    19 дек 2016 - 15:11

Лицензия


Яндекс.Метрика