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 Буравчик . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.