[python] Подскажите библиотеку распарсить иерархический CSV файл на Python
От: Antei США  
Дата: 22.09.22 13:28
Оценка:
Добрый день, форум!

Есть текстовый файл вот такого формата:
(CSV, с иерархической логической зависимостью между строками)

я сделал отступы просто чтобы показать иерархию, но в оригинальном файле, понятно что отступов нет
"HEADER", "headerCol1", "headerCol2", ...
   "SUMMARY", "summaryCol1", "summaryCol2", ...
   "BLOCK_TYPE1_HEADER", "blockType1HeaderCol1", "blockType1HeaderCol2", ...
      "BLOCK_TYPE1_CONTENT", "blockType1ContentCol1", "blockType1ContentCol2", ...
      "BLOCK_TYPE1_CONTENT", "blockType1ContentCol1", "blockType1ContentCol2", ...
      "BLOCK_TYPE1_CONTENT", "blockType1ContentCol1", "blockType1ContentCol2", ...
   "BLOCK_TYPE2_HEADER", "blockType2HeaderCol1", "blockType2HeaderCol2", ...
      "BLOCK_TYPE2_CONTENT", "blockType2ContentCol1", "blockType2ContentCol2", ...
      "BLOCK_TYPE2_CONTENT", "blockType2ContentCol1", "blockType2ContentCol2", ...
"TRAILER", "headerCol1", "headerCol2", ...
"HEADER", ...
   ...
"TRAILER", ...


Посоветуйте подход в Python, библиотеку(ки), где можно бы декларативно описать такую структуру и получать на выходе парсера "гирлянду" из распарсенных структур?

Спасибо!
python3 python parser csv hierarchy
Re: [python] Подскажите библиотеку распарсить иерархический CSV файл на Python
От: · Великобритания  
Дата: 22.09.22 17:59
Оценка: +2
Здравствуйте, Antei, Вы писали:

A> Посоветуйте подход в Python, библиотеку(ки), где можно бы декларативно описать такую структуру и получать на выходе парсера "гирлянду" из распарсенных структур?

Эээ.. Ну это какой-то кастомный формат, для него нужен кастомный парсер. Если совсем уж так декларативно, то берёшь какую-нибудь либу LR-парсера и фигачишь грамматику. Но, имхо, это из пушки по воробьям.
Вроде можно немножко кастомизировать построчный csv-парсер, решение будет на десяток строк от силы.

Или я не понял, что ты хочешь от "декларативно".

Ещё как вариант, дать по кумполу товарищам, которые выдумали ещё один гениальный формат, и попросить их использовать какой-нибудь json.
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[2]: [python] Подскажите библиотеку распарсить иерархический CSV файл на Pytho
От: Antei США  
Дата: 22.09.22 19:48
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, Antei, Вы писали:


A>> Посоветуйте подход в Python, библиотеку(ки), где можно бы декларативно описать такую структуру и получать на выходе парсера "гирлянду" из распарсенных структур?

·>Эээ.. Ну это какой-то кастомный формат, для него нужен кастомный парсер. Если совсем уж так декларативно, то берёшь какую-нибудь либу LR-парсера и фигачишь грамматику. Но, имхо, это из пушки по воробьям.
·>Вроде можно немножко кастомизировать построчный csv-парсер, решение будет на десяток строк от силы.
Это один из нескольких десятков файлов, которые прийдётся онбоардить.
Все файлы подходят под описанную структуру.
Поэтому и хочется сделать слой который сможет по описанию парсить, чтобы сократить время онбоардинга фида.

·>Или я не понял, что ты хочешь от "декларативно".

Да, хотелось бы где-нибудь (yaml, json, ...) описать грамматику типа на какие колонки разбит каждый тип строки и как эти строки связаны между собой.
Я новичок в Python, пока пытаюсь найти подходящие решения/либы.
Прежде чем ринуться писать свой велосипед )

·>Ещё как вариант, дать по кумполу товарищам, которые выдумали ещё один гениальный формат, и попросить их использовать какой-нибудь json.

Внешний фид, ничего не сделаешь.
Re[3]: [python] Подскажите библиотеку распарсить иерархический CSV файл на Pytho
От: · Великобритания  
Дата: 22.09.22 20:24
Оценка: 4 (1)
Здравствуйте, Antei, Вы писали:

A> ·>Вроде можно немножко кастомизировать построчный csv-парсер, решение будет на десяток строк от силы.

A> Это один из нескольких десятков файлов, которые прийдётся онбоардить.
A> Все файлы подходят под описанную структуру.
A> Поэтому и хочется сделать слой который сможет по описанию парсить, чтобы сократить время онбоардинга фида.
Я не думаю, что есть что-нибудь готовое... Наваять штуку, которая по описанию ожидаемых полей будет хитро парсить csv... ну 20 строчек выйдет, имхо.

A> ·>Или я не понял, что ты хочешь от "декларативно".

A> Да, хотелось бы где-нибудь (yaml, json, ...) описать грамматику типа на какие колонки разбит каждый тип строки и как эти строки связаны между собой.
Я бы описывал такую "грамматику" на самом питоне. Легче будет всякие кастомизации прикручивать. Например, легко будет добавить кусочек кода в виде лямбды для парсинга хитрого типа колонки.

A> Я новичок в Python, пока пытаюсь найти подходящие решения/либы.

A> Прежде чем ринуться писать свой велосипед )
Уж очень велосипедный формат сам по себе. В лучшем случае может есть что-то у разработчиков этого самого внешнего фида.

A> Внешний фид, ничего не сделаешь.

Сочувствую.
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: [python] Подскажите библиотеку распарсить иерархический
От: Буравчик Россия  
Дата: 23.09.22 06:39
Оценка: 18 (2) +1
Здравствуйте, Antei, Вы писали:

A>Посоветуйте подход в Python, библиотеку(ки), где можно бы декларативно описать такую структуру и получать на выходе парсера "гирлянду" из распарсенных структур?


disclaimer: Раньше с парсерами на основе грамматик на питоне не работал

Посмотрел, какие парсеры на основе грамматик есть в питоне. Оказалось, что их куча.
Статью нашел (но особо не вникал) https://tomassetti.me/parsing-in-python/

Дальше руки зачесались их попробовать. И это в час ночи!!!
Нашел первый попавшийся парсер с описанием грамматик в виде BNF и с большим количеством звезд гитхаба.

Выбор пал на Lark (https://github.com/lark-parser/lark) и получилось такое:
  Код
from dataclasses import dataclass
from pprint import pprint
from typing import List, Optional

from lark import Lark, Transformer

file_content = """
"HEADER", "headerCol1", "headerCol2"
"SUMMARY", "summaryCol1", "summaryCol2"
"BLOCK_TYPE1_HEADER", "blockType1HeaderCol1", "blockType1HeaderCol2"
"BLOCK_TYPE1_CONTENT", "blockType1ContentCol1", "blockType1ContentCol2"
"BLOCK_TYPE1_CONTENT", "blockType1ContentCol1", "blockType1ContentCol2"
"BLOCK_TYPE1_CONTENT", "blockType1ContentCol1", "blockType1ContentCol2"
"BLOCK_TYPE2_HEADER", "blockType2HeaderCol1", "blockType2HeaderCol2"
"BLOCK_TYPE2_CONTENT", "blockType2ContentCol1", "blockType2ContentCol2"
"BLOCK_TYPE2_CONTENT", "blockType2ContentCol1", "blockType2ContentCol2"
"TRAILER", "headerCol1", "headerCol2"
"HEADER", "headerCol1", "headerCol2"
"TRAILER", "headerCol1", "headerCol2"
"""

file_grammar = """
    start: header_trailer* NEWLINE*

    header_trailer: header (summary | block_type1 | block_type2)* trailer -> header_children_trailer
    block_type1: block_type1_header block_type1_content* -> header_children
    block_type2: block_type2_header block_type2_content* -> header_children
    
    header: NEWLINE* /"HEADER"/ values -> line
    trailer: NEWLINE* /"TRAILER"/ values -> line
    summary: NEWLINE* /"SUMMARY"/ values -> line
    block_type1_header: NEWLINE* /"BLOCK_TYPE1_HEADER"/ values -> line
    block_type1_content: NEWLINE* /"BLOCK_TYPE1_CONTENT"/ values -> line
    block_type2_header: NEWLINE* /"BLOCK_TYPE2_HEADER"/ values -> line
    block_type2_content: NEWLINE* /"BLOCK_TYPE2_CONTENT"/ values -> line
    
    values: ("," ESCAPED_STRING)* -> values
    
    %import common.WS_INLINE
    %import common.NEWLINE
    %import common.ESCAPED_STRING
    %ignore WS_INLINE
"""


class Item:
    pass


@dataclass
class Line(Item):
    name: str
    params: List[str]


@dataclass
class Block(Item):
    header: Line
    children: List[Item]
    trailer: Optional[Line]


def strip(s):
    return s.lstrip('"').rstrip('"')


# noinspection PyMethodMayBeStatic
class MyTransformer(Transformer):
    def values(self, tokens):
        return [strip(t.value) for t in tokens]

    def line(self, tokens):
        return Line(name=strip(tokens[-2].value), params=tokens[-1])

    def header_children(self, tokens):
        return Block(header=tokens[0], children=tokens[1:], trailer=None)

    def header_children_trailer(self, tokens):
        return Block(header=tokens[0], children=tokens[1:-1], trailer=tokens[-1])


def main():
    file_parser = Lark(file_grammar)
    tree = file_parser.parse(file_content)
    r = MyTransformer().transform(tree)
    pprint(r.children)


if __name__ == '__main__':
    main()


  Результат
[Block(header=Line(name='HEADER', params=['headerCol1', 'headerCol2']),
       children=[Line(name='SUMMARY', params=['summaryCol1', 'summaryCol2']),
                 Block(header=Line(name='BLOCK_TYPE1_HEADER',
                                   params=['blockType1HeaderCol1',
                                           'blockType1HeaderCol2']),
                       children=[Line(name='BLOCK_TYPE1_CONTENT',
                                      params=['blockType1ContentCol1',
                                              'blockType1ContentCol2']),
                                 Line(name='BLOCK_TYPE1_CONTENT',
                                      params=['blockType1ContentCol1',
                                              'blockType1ContentCol2']),
                                 Line(name='BLOCK_TYPE1_CONTENT',
                                      params=['blockType1ContentCol1',
                                              'blockType1ContentCol2'])],
                       trailer=None),
                 Block(header=Line(name='BLOCK_TYPE2_HEADER',
                                   params=['blockType2HeaderCol1',
                                           'blockType2HeaderCol2']),
                       children=[Line(name='BLOCK_TYPE2_CONTENT',
                                      params=['blockType2ContentCol1',
                                              'blockType2ContentCol2']),
                                 Line(name='BLOCK_TYPE2_CONTENT',
                                      params=['blockType2ContentCol1',
                                              'blockType2ContentCol2'])],
                       trailer=None)],
       trailer=Line(name='TRAILER', params=['headerCol1', 'headerCol2'])),
 Block(header=Line(name='HEADER', params=['headerCol1', 'headerCol2']),
       children=[],
       trailer=Line(name='TRAILER', params=['headerCol1', 'headerCol2'])),
 Token('NEWLINE', '\n')]  <- этот висячий токен лень удалять
Best regards, Буравчик
Отредактировано 23.09.2022 6:46 Буравчик . Предыдущая версия . Еще …
Отредактировано 23.09.2022 6:40 Буравчик . Предыдущая версия .
Re[2]: [python] Подскажите библиотеку распарсить иерархический
От: Antei США  
Дата: 23.09.22 16:14
Оценка:
Здравствуйте, Буравчик, Вы писали:

Не знаю где вы берёте время так подробно отвечать на вопрос, огромное спасибо за ответ!

Б>disclaimer: Раньше с парсерами на основе грамматик на питоне не работал


Б>Посмотрел, какие парсеры на основе грамматик есть в питоне. Оказалось, что их куча.

Б>Статью нашел (но особо не вникал) https://tomassetti.me/parsing-in-python/
Да, я тоже уже натыкался на эту статью, оказалась полезной.


Б>Дальше руки зачесались их попробовать. И это в час ночи!!!

Б>Нашел первый попавшийся парсер с описанием грамматик в виде BNF и с большим количеством звезд гитхаба.
Супер, буду его смотреть, для начала то что надо!
Еще раз спасибо!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.