Не удается правильно разобрать этот файл с помощью pyparsing

Я пытаюсь разобрать файл, используя замечательную библиотеку Python pyparsing, но у меня много проблем...

Файл, который я пытаюсь разобрать, выглядит примерно так:

sectionOne:
  list:
  - XXitem
  - XXanotherItem
  key1: value1
  product: milk
  release: now
  subSection:
    skey : sval
    slist:
    - XXitem
  mods:
  - XXone
  - XXtwo
  version: last
sectionTwo:
  base: base-0.1
  config: config-7.0-7

Как видите, это файл конфигурации с отступом, и примерно так я пытался определить грамматику.

  • Файл может иметь один или несколько разделов
  • Каждый раздел формируется именем раздела и содержанием раздела.
  • Каждый раздел имеет отступ содержимого
  • Содержимое каждого раздела может иметь одну или несколько пар ключ/значение или подраздел.
  • Каждое значение может быть просто одним словом или списком элементов.
  • Список элементов представляет собой группу из одного или нескольких элементов.
  • Каждый элемент представляет собой ДЕФИС + имя, начинающееся с «XX».

Я пытался создать эту грамматику с помощью pyparsing, но безуспешно.

import pprint
import pyparsing
NEWLINE = pyparsing.LineEnd().suppress()
VALID_CHARACTERS = pyparsing.srange("[a-zA-Z0-9_\-\.]")
COLON = pyparsing.Suppress(pyparsing.Literal(":"))
HYPHEN = pyparsing.Suppress(pyparsing.Literal("-"))
XX = pyparsing.Literal("XX")

list_item = HYPHEN + pyparsing.Combine(XX + pyparsing.Word(VALID_CHARACTERS))
list_of_items = pyparsing.Group(pyparsing.OneOrMore(list_item))

key = pyparsing.Word(VALID_CHARACTERS) + COLON
pair_value = pyparsing.Word(VALID_CHARACTERS) + NEWLINE
value = (pair_value | list_of_items)

pair = pyparsing.Group(key + value)

indentStack = [1]

section = pyparsing.Forward()
section_name = pyparsing.Word(VALID_CHARACTERS) + COLON
section_value = pyparsing.OneOrMore(pair | section)
section_content = pyparsing.indentedBlock(section_value, indentStack, True)

section << pyparsing.Group(section_name + section_content)

parser = pyparsing.OneOrMore(section)

def main():
    try:
        with open('simple.info', 'r') as content_file:
            content = content_file.read()

            print "content:\n", content
            print "\n"
            result = parser.parseString(content)
            print "result1:\n", result
            print "len", len(result)

            pprint.pprint(result.asList())
    except pyparsing.ParseException, err:
        print err.line
        print " " * (err.column - 1) + "^"
        print err
    except pyparsing.ParseFatalException, err:
        print err.line
        print " " * (err.column - 1) + "^"
        print err


if __name__ == '__main__':
    main()

Это результат:

result1:
  [['sectionOne', [[['list', ['XXitem', 'XXanotherItem']], ['key1', 'value1'], ['product', 'milk'], ['release', 'now'], ['subSection', [[['skey', 'sval'], ['slist', ['XXitem']], ['mods', ['XXone', 'XXtwo']], ['version', 'last']]]]]]], ['sectionTwo', [[['base', 'base-0.1'], ['config', 'config-7.0-7']]]]]
  len 2
  [
     ['sectionOne',
     [[
        ['list', ['XXitem', 'XXanotherItem']],
        ['key1', 'value1'],
        ['product', 'milk'],
        ['release', 'now'],
        ['subSection',
           [[
              ['skey', 'sval'],
              ['slist', ['XXitem']],
              ['mods', ['XXone', 'XXtwo']],
              ['version', 'last']
           ]]
        ]
     ]]
     ],
     ['sectionTwo', 
     [[
        ['base', 'base-0.1'], 
        ['config', 'config-7.0-7']
     ]]
     ]
  ]

Как видите, у меня две основные проблемы:

1.- Содержимое каждого раздела дважды вложено в список.

2.- ключ «версия» анализируется внутри «подраздела», когда он принадлежит «разделу один».

Моя настоящая цель состоит в том, чтобы иметь возможность получить структуру вложенных словарей Python с ключами и значениями, чтобы легко извлекать информацию для каждого поля, но pyparsing.Dict для меня что-то неясное.

Кто-нибудь может мне помочь?

заранее спасибо

( Простите за длинный пост )


person thamurath    schedule 26.06.2013    source источник
comment
Ваш формат конфигурации выглядит как YAML. Разве вы не можете просто использовать PyYAML вместо разбора вручную?   -  person Nikita Nemkin    schedule 26.06.2013
comment
Спасибо за комментарий @NikitaNemkin. Да, я мог бы использовать PyYAM, на самом деле это то, что мы используем прямо сейчас, но файлы, которые мне нужно разобрать, исходят от заинтересованного лица, которое, как правило, вносит незначительные изменения, поэтому мы хотим разработать собственный синтаксический анализатор, чтобы иметь возможность изменять это соответственно   -  person thamurath    schedule 26.06.2013


Ответы (1)


Вы действительно довольно близки - поздравляю, парсеры с отступами не так просто писать с помощью pyparsing.

Посмотрите на прокомментированные изменения. Те, что отмечены буквой «А», — это изменения, направленные на устранение двух указанных вами проблем. Те, которые отмечены «B», добавляют конструкции Dict, чтобы вы могли получить доступ к проанализированным данным как к вложенной структуре, используя имена в конфигурации.

Самая большая проблема заключается в том, что indentedBlock выполняет дополнительную группировку для вас, что мешает ассоциациям имени и значения Dict. Использование ungroup для удаления этого позволяет Dict видеть основные пары.

Удачи в pyparsing!

import pprint
import pyparsing
NEWLINE = pyparsing.LineEnd().suppress()
VALID_CHARACTERS = pyparsing.srange("[a-zA-Z0-9_\-\.]")
COLON = pyparsing.Suppress(pyparsing.Literal(":"))
HYPHEN = pyparsing.Suppress(pyparsing.Literal("-"))
XX = pyparsing.Literal("XX")

list_item = HYPHEN + pyparsing.Combine(XX + pyparsing.Word(VALID_CHARACTERS))
list_of_items = pyparsing.Group(pyparsing.OneOrMore(list_item))

key = pyparsing.Word(VALID_CHARACTERS) + COLON
pair_value = pyparsing.Word(VALID_CHARACTERS) + NEWLINE
value = (pair_value | list_of_items)

#~ A: pair = pyparsing.Group(key + value)
pair = (key + value)

indentStack = [1]

section = pyparsing.Forward()
section_name = pyparsing.Word(VALID_CHARACTERS) + COLON
#~ A: section_value = pyparsing.OneOrMore(pair | section)
section_value = (pair | section)

#~ B: section_content = pyparsing.indentedBlock(section_value, indentStack, True)
section_content = pyparsing.Dict(pyparsing.ungroup(pyparsing.indentedBlock(section_value, indentStack, True)))

#~ A: section << Group(section_name + section_content)
section << (section_name + section_content)

#~ B: parser = pyparsing.OneOrMore(section)
parser = pyparsing.Dict(pyparsing.OneOrMore(pyparsing.Group(section)))

Теперь вместо pprint(result.asList()) можно написать:

print (result.dump())

чтобы показать иерархию Dict:

[['sectionOne', ['list', ['XXitem', 'XXanotherItem']], ... etc. ...
- sectionOne: [['list', ['XXitem', 'XXanotherItem']], ... etc. ...
  - key1: value1
  - list: ['XXitem', 'XXanotherItem']
  - mods: ['XXone', 'XXtwo']
  - product: milk
  - release: now
  - subSection: [['skey', 'sval'], ['slist', ['XXitem']]]
    - skey: sval
    - slist: ['XXitem']
  - version: last
- sectionTwo: [['base', 'base-0.1'], ['config', 'config-7.0-7']]
  - base: base-0.1
  - config: config-7.0-7

позволяя вам писать такие утверждения, как:

print (result.sectionTwo.base)
person PaulMcG    schedule 29.06.2013
comment
Спасибо за ваш быстрый ответ. Он отлично работает прямо сейчас. Ваша библиотека очень интересная, только немного не хватает документации. Еще раз спасибо и очень красивая работа! - person thamurath; 01.07.2013