парсинг файла с фигурными скобками

Мне нужно разобрать файл с информацией, разделенной фигурными скобками, например:

Continent
{
Name    Europe
Country
{
Name    UK
Dog
{
Name    Fiffi
Colour  Gray
}
Dog
{
Name    Smut
Colour  Black
}
}
}

Вот что я пробовал в Python

from io import open
from pyparsing import *
import pprint

def parse(s):
    return nestedExpr('{','}').parseString(s).asList()

def test(strng):
    print strng
    try:
        cfgFile = file(strng)
        cfgData = "".join( cfgFile.readlines() )
        list = parse( cfgData )
        pp = pprint.PrettyPrinter(2)
        pp.pprint(list)

    except ParseException, err:
        print err.line
        print " "*(err.column-1) + "^"
        print err

    cfgFile.close()
    print
    return list

if __name__ == '__main__':
    test('testfile')

Но это не удается с ошибкой:

testfile
Continent
^
Expected "{" (at char 0), (line:1, col:1)

Traceback (most recent call last):
  File "xxx.py", line 55, in <module>
    test('testfile')
  File "xxx.py", line 40, in test
    return list
UnboundLocalError: local variable 'list' referenced before assignment  

Что мне нужно сделать, чтобы это сработало? Является ли другой парсер лучше, чем pyparsing?


person Damian    schedule 06.06.2013    source источник
comment
не по теме, но лучше не использовать list в качестве имени переменной, так как оно зарезервировано в python   -  person oleg    schedule 06.06.2013
comment
Я могу предложить Вам решение с алгоритмом на основе рекурсии. Соответствует ли он вашим требованиям (разделить все данные на '{' один раз и rsplit на '}' один раз. Внешняя часть должна быть проанализирована как ключ-значение для возвращаемого словаря, а внутренняя часть должна быть передана самой рекурсивной функции. и так далее.. .)   -  person oleg    schedule 06.06.2013
comment
Пожалуйста, покажите, что вы хотите получить в результате.   -  person Mike Müller    schedule 06.06.2013
comment
Почему голосование против вопроса? Это требует, возможно, некоторой дополнительной информации, такой как ожидаемый результат ... но, тем не менее, интересно ???   -  person Sylvain Leroux    schedule 06.06.2013


Ответы (2)


Ключевым здесь является рекурсивность. Попробуйте что-нибудь вокруг этого:

def parse(it):
    result = []
    while True:
        try:
            tk = next(it)
        except StopIteration:
            break

        if tk == '}':
            break
        val = next(it)
        if val == '{':
            result.append((tk,parse(it)))
        else:
            result.append((tk, val))

    return result

Вариант использования:

import pprint       

data = """
Continent
{
Name    Europe
Country
{
Name    UK
Dog
{
Name    Fiffi
Colour  Gray
}
Dog
{
Name    Smut
Colour  Black
}
}
}
"""

r = parse(iter(data.split()))
pprint.pprint(r)

... которые производят (Python 2.6):

[('Continent',
  [('Name', 'Europe'),
   ('Country',
    [('Name', 'UK'),
     ('Dog', [('Name', 'Fiffi'), ('Colour', 'Gray')]),
     ('Dog', [('Name', 'Smut'), ('Colour', 'Black')])])])]

Пожалуйста, примите это как отправную точку и не стесняйтесь улучшать код по мере необходимости (в зависимости от ваших данных, возможно, словарь был бы лучшим выбором). Кроме того, пример кода не обрабатывает должным образом неправильно сформированные данные (в частности, дополнительные или отсутствующие } — я настоятельно рекомендую вам выполнить полное тестовое покрытие;)


РЕДАКТИРОВАТЬ: Обнаружив pyparsing, я попробовал следующее, которое работает (намного) лучше и может быть (более) легко адаптировано для особых нужд:

import pprint
from pyparsing import Word, Literal, Forward, Group, ZeroOrMore, alphas

def syntax():
    lbr = Literal( '{' ).suppress()
    rbr = Literal( '}' ).suppress()
    key = Word( alphas )
    atom = Word ( alphas )
    expr = Forward()
    pair = atom | (lbr + ZeroOrMore( expr ) + rbr)
    expr << Group ( key + pair )

    return expr

expr = syntax()
result = expr.parseString(data).asList()
pprint.pprint(result)

Производство:

[['Continent',
  ['Name', 'Europe'],
  ['Country',
   ['Name', 'UK'],
   ['Dog', ['Name', 'Fiffi'], ['Colour', 'Gray']],
   ['Dog', ['Name', 'Smut'], ['Colour', 'Black']]]]]
person Sylvain Leroux    schedule 06.06.2013
comment
Использование split() для разбиения токенов работает для этого примера данных, но не все входные данные могут быть так хорошо отформатированы. split() завершится ошибкой, если конечные фигурные скобки введены как }}}, например, или если какая-либо запись содержит фигурные скобки внутри строки в кавычках. - person PaulMcG; 06.06.2013
comment
@PaulMcGuire, это правильно. Первоначальный вопрос не определяет полностью формат входных данных. Что касается пробелов вокруг фигурных скобок, регулярное выражение поможет. Если грамматика более сложная, например, как вы сказали, содержащая строки в кавычках - pyparsing все равно будет лучше. - person Sylvain Leroux; 07.06.2013
comment
Хорошая работа, это более строгий синтаксический анализатор, чем просто использование nestedExpr, вы сохраняете отношения ключ + значение. Добро пожаловать в pyparsing, Сильвен! - person PaulMcG; 07.06.2013

Вложенные выражения очень распространены и обычно требуют определений рекурсивного синтаксического анализатора или рекурсивного кода, если вы не используете библиотеку синтаксического анализа. Этот код может быть пугающим для новичков и подверженным ошибкам даже для экспертов, поэтому я добавил помощника nestedExpr в pyparsing.

Проблема, с которой вы столкнулись, заключается в том, что ваша входная строка содержит больше, чем просто выражение вложенных фигурных скобок. Когда я впервые пробую синтаксический анализатор, я стараюсь максимально упростить тестирование — т. е., например, я встраиваю образец вместо того, чтобы читать его из файла.

test = """\
Continent
{
Name    Europe
Country
{
Name    UK
Dog
{
Name    Fiffi
Colour  "light Gray"
}
Dog
{
Name    Smut
Colour  Black
}}}"""

from pyparsing import *

expr = nestedExpr('{','}')

print expr.parseString(test).asList()

И я получаю ту же ошибку синтаксического анализа, что и вы:

Traceback (most recent call last):
  File "nb.py", line 25, in <module>
    print expr.parseString(test).asList()
  File "c:\python26\lib\site-packages\pyparsing-1.5.7-py2.6.egg\pyparsing.py", line 1006, in parseString
    raise exc
pyparsing.ParseException: Expected "{" (at char 1), (line:1, col:1)

Итак, глядя на сообщение об ошибке (и даже на ваш собственный код отладки), pyparsing натыкается на ведущее слово «Континент», потому что это слово не является началом вложенного выражения в фигурных скобках, pyparsing (как мы видим в сообщении об исключении ) искал открытие '{'.

Решение состоит в том, чтобы немного изменить ваш синтаксический анализатор для обработки вводной метки «Континент», изменив expr на:

expr = Word(alphas) + nestedExpr('{','}')

Теперь распечатка результатов в виде списка (используя pprint, как это сделано в OP, хорошая работа) выглядит так:

['Continent',
 ['Name',
  'Europe',
  'Country',
  ['Name',
   'UK',
   'Dog',
   ['Name', 'Fiffi', 'Colour', '"light Gray"'],
   'Dog',
   ['Name', 'Smut', 'Colour', 'Black']]]]

который должен совпадать с вложением фигурной скобки.

person PaulMcG    schedule 06.06.2013
comment
Я обнаружил pyparsing благодаря этому вопросу - не так много документации в Интернете, но действительно очень интересный модуль. Есть ли способ создать словарь вместо вложенных списков? - person Sylvain Leroux; 07.06.2013