Pyparsing: анализ вложенных данных открытого текста с ключом = значением - PullRequest
0 голосов
/ 26 июня 2018

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

string2 = """
object1 {
        key1 = value1
        key2 = value2
        #key3 = value3
        key4 = value4
        #key5 = value5
        key6 = value6
        subobject1 {
            key1 = value1
            key2 = value2
            key3 = value3
        }
}
"""

И я могу получить пару ключ = значение, используя этот код

def parse_objects(source):
    LBRACE,EQ,RBRACE,HASH = map(Suppress, '{=}#')
    object_name = Word(printables)
    #disable = MatchFirst(map(Literal, '#'.split()))
    key = Word(printables)
    value = Word(printables)

    if LineStart() == HASH:
        key_and_value = Group(HASH + key('key') + EQ + value('value'))
    else:
        key_and_value = Group(key('key') + EQ + value('value'))

    collection = Forward()
    object_body = Group(LBRACE + ZeroOrMore(collection | key_and_value) + RBRACE)
    collection <<= Group(object_name + object_body)

    return collection.parseString(source)

collection = parse_objects(string2)
print(collection.dump())

Но мне также нужно проанализировать данные, которые не содержат значений в объектах, только ключи. Например

object1 {
        key1 = value1
        key2
        #key3 = value3
        key4
        #key5 = value5
        key6 = value6
        subobject1 {
            key1 = value1
            key2 = value2
            key3 = value3
        }
}

Я попытался внести изменения в код и добавить проверочное выражение if value is None. Как то так

if value is None:
    key_and_value = Group(key('key'))
else:
    if LineStart() == HASH:
        key_and_value = Group(HASH + key('key') + EQ + value('value'))
    else:
        key_and_value = Group(key('key') + EQ + value('value'))

но я получаю ошибку

Match W:(0123...) at loc 19(3,9)
Matched W:(0123...) -> ['key1']
Match W:(0123...) at loc 25(3,15)
Matched W:(0123...) -> ['value1']
Match W:(0123...) at loc 41(4,9)
Matched W:(0123...) -> ['key2']
Traceback (most recent call last):
  File "c:\Python27\my_projects\test_parser.py", line 86, in <module>
    collection = parse_objects(string2)
  File "c:\Python27\my_projects\test_parser.py", line 84, in parse_objects
    return collection.parseString(source)
  File "C:\Python27\lib\site-packages\pyparsing.py", line 1632, in parseString
    raise exc
ParseException: Expected "}" (at char 41), (line:4, col:9)

Я думаю, что pyparsing принимает ключ как подобъект и не находит {. Кто-нибудь может дать мне какие-нибудь советы? Может быть, мне нужно изменить свой подход к грамматике? Я ценю любую помощь.

Редактировать 1

@ Решение Jappy отлично работает для данных, которые я написал выше, когда раздел subobject1 находится внизу основного раздела. Проанализировав мои данные, я обнаружил, что после раздела subobject1 может быть больше пар ключ = значение или только ключи, что-то вроде этого:

string2 = """
object1 {
        key1 = value1
        key2
        #key3 = value3
        key4 = value4
        subobject1 {
            key1 = value1
            key2 = value2
            key3 = value3
        }        
        #key5 = value5
        key6 = v_a_l_u_e_6
        subobject2 {
            key1 = value1
        }
        key7 = value7
        key8
}
"""

Вывод будет следующим:

[['object1', ['key1', 'value1'], ['key2', 'null'], ['#key3', 'value3'], ['key4', 'value4'], ['subobject1', ['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']], ['#key5', 'value5'], ['key6', 'v_a_l_u_e_6'], ['subobject2', ['key1', 'value1']], ['key7', 'value7'], ['key8', 'null']]]
- objects: ['object1', ['key1', 'value1'], ['key2', 'null'], ['#key3', 'value3'],
['key4', 'value4'], ['subobject1', ['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']], ['#key5', 'value5'], ['key6', 'v_a_l_u_e_6'], ['subobject2', ['key1', 'value1']], ['key7', 'value7'], ['key8', 'null']]
  - key_val_lines: [['key7', 'value7'], ['key8', 'null']]
    [0]:
      ['key7', 'value7']
      - key: 'key7'
      - val: 'value7'
    [1]:
      ['key8', 'null']
      - key: 'key8'
      - val: 'null'
  - obj_name: 'object1'
  - objects: ['subobject2', ['key1', 'value1']]
    - key_val_lines: [['key1', 'value1']]
      [0]:
        ['key1', 'value1']
        - key: 'key1'
        - val: 'value1'
    - obj_name: 'subobject2'

Я изменил код следующим образом:

ParserElement.inlineLiteralsUsing(Suppress)
name_expr = Word(printables, excludeChars='{}')
key_val_expr = '=' + Word(printables)

key_val_line = Group(name_expr('key') + (lineEnd().setParseAction(lambda t: 'null') | key_val_expr)('val'))
#key_val_lines = OneOrMore(key_val_line)('key_val_lines')

obj = Forward()
objects = Group('{' + OneOrMore(key_val_line | obj) + '}')
obj << Group(name_expr('obj_name') + objects('objects'))
#obj << Group(name_expr('obj_name') + '{' + OneOrMore(key_val_lines | obj) + '}')('objects')

o = obj.parseString(string2)
print o.dump()

И результат:

[['object1', [['key1', 'value1'], ['key2', 'null'], ['#key3', 'value3'], ['key4',
'value4'], ['subobject1', [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]], ['#key5', 'value5'], ['key6', 'v_a_l_u_e_6'], ['subobject2', [['key1', 'value1']]], ['key7', 'value7'], ['key8', 'null']]]]
[0]:
  ['object1', [['key1', 'value1'], ['key2', 'null'], ['#key3', 'value3'], ['key4', 'value4'], ['subobject1', [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]], ['#key5', 'value5'], ['key6', 'v_a_l_u_e_6'], ['subobject2', [['key1', 'value1']]], ['key7', 'value7'], ['key8', 'null']]]
  - obj_name: 'object1'
  - objects: [['key1', 'value1'], ['key2', 'null'], ['#key3', 'value3'], ['key4',
'value4'], ['subobject1', [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]], ['#key5', 'value5'], ['key6', 'v_a_l_u_e_6'], ['subobject2', [['key1', 'value1']]], ['key7', 'value7'], ['key8', 'null']]
    [0]:
      ['key1', 'value1']
      - key: 'key1'
      - val: 'value1'
    [1]:
      ['key2', 'null']
      - key: 'key2'
      - val: 'null'
    [2]:
      ['#key3', 'value3']
      - key: '#key3'
      - val: 'value3'
    [3]:
      ['key4', 'value4']
      - key: 'key4'
      - val: 'value4'
    [4]:
      ['subobject1', [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]]
      - obj_name: 'subobject1'
      - objects: [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]
        [0]:
          ['key1', 'value1']
          - key: 'key1'
          - val: 'value1'
        [1]:
          ['key2', 'value2']
          - key: 'key2'
          - val: 'value2'
        [2]:
          ['key3', 'value3']
          - key: 'key3'
          - val: 'value3'
    [5]:
      ['#key5', 'value5']
      - key: '#key5'
      - val: 'value5'
    [6]:
      ['key6', 'v_a_l_u_e_6']
      - key: 'key6'
      - val: 'v_a_l_u_e_6'
    [7]:
      ['subobject2', [['key1', 'value1']]]
      - obj_name: 'subobject2'
      - objects: [['key1', 'value1']]
        [0]:
          ['key1', 'value1']
          - key: 'key1'
          - val: 'value1'
    [8]:
      ['key7', 'value7']
      - key: 'key7'
      - val: 'value7'
    [9]:
      ['key8', 'null']
      - key: 'key8'
      - val: 'null'

Но я не мог setResultsName вместо группы [0] index:

obj << Group(name_expr('obj_name') + objects('objects'))('section')

возвращает неверный результат.

Ответы [ 3 ]

0 голосов
/ 01 июля 2018

@ В ответе Джеппи есть несколько отличных предложений. Я бы добавил:

  • Word(printables) всегда рискованная конструкция, так как она будет соответствовать какому большему количеству непробельных символов, как есть. Например, если строка содержит «color = red» без пробелов, она будет интерпретирована как ключ «color = red» без значения. Вам было бы лучше определить ключ с чем-то вроде Word(alphanums) или Word(alphas, alphanums+"_"). Чтобы учесть возможный ведущий '#', используйте Word(alphas+'#', alphanums+"_").

  • Ваша идея об обусловленности присутствия '#' с помощью if LineStart() == HASH интересна, но не так, как работает pyparsing. На этом этапе кода вы все еще строите сам синтаксический анализатор, что происходит отдельно от любого входного текста. Фактическое определение того, начинается ли конкретная строка с «#», происходит во время синтаксического анализа, что делается позже, когда ваш код вызывает collection.parseString. То есть вы создаете все биты синтаксического анализатора, а затем указываете их на исходный текст. Любая логика «если символ X присутствует» должна быть представлена ​​с использованием некоторого чередования или необязательной конструкции в самом синтаксическом анализаторе, а не с помощью кода Python if-then.

  • Рассмотрите возможность использования необязательного класса pyparsing для элементов, которые могут присутствовать или не присутствовать. Это относится к возможному key-value без значения, а также может быть другим способом обработки возможного начального символа '#' в именах ключей.

0 голосов
/ 03 июля 2018

Рекурсивные синтаксические анализаторы - непростая задача для начала работы с pyparsing, а ваши дополнительные биты также усложняют ситуацию. Я думаю, что этот код в основном делает то, что вы хотите - надеюсь, теперь он станет более значимым для вас, когда вы до сих пор боролись с pyparsing:

import pyparsing as pp

LBRACE, RBRACE, EQ = map(pp.Suppress, "{}=")
# convert parsed '#' to a bool that you can test on
disabled_marker = pp.Literal("#").addParseAction(lambda: True)
identifier = pp.pyparsing_common.identifier
key = identifier()

# try to parse a numeric value first, might be interesting
# pyparsing_common.number will auto-convert string to float or int at parse time,
# so you won't have to detect and do the conversion later
value = pp.pyparsing_common.number | pp.Word(pp.printables)

obj_item = pp.Forward()
obj_expr = pp.Group(identifier("name")
                    + pp.Group(LBRACE
                               + pp.ZeroOrMore(obj_item)
                               + RBRACE)("attributes"))

key_with_value = pp.Group(pp.Optional(disabled_marker)("disabled")
                          + key("key") + EQ + value("value"))
# use empty() to inject a None for the value
key_without_value = pp.Group(pp.Optional(disabled_marker)("disabled")
                             + key("key") 
                             + pp.empty().addParseAction(lambda: [None])("value"))

# now define an item that can be used in an object - this order is important!
obj_item <<= obj_expr | key_with_value | key_without_value

Для анализа вашего string2 ввода:

zz = obj_expr.parseString(string2)
print(zz[0].dump())

Дает:

['object1', [['key1', 'value1'], ['key2', None], [True, 'key3', 'value3'], ['key4', 'value4'], ['subobject1', [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]], [True, 'key5', 'value5'], ['key6', 'v_a_l_u_e_6'], ['subobject2', [['key1', 'value1']]], ['key7', 'value7'], ['key8', None]]]
- attributes: [['key1', 'value1'], ['key2', None], [True, 'key3', 'value3'], ['key4', 'value4'], ['subobject1', [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]], [True, 'key5', 'value5'], ['key6', 'v_a_l_u_e_6'], ['subobject2', [['key1', 'value1']]], ['key7', 'value7'], ['key8', None]]
  [0]:
    ['key1', 'value1']
    - key: 'key1'
    - value: 'value1'
  [1]:
    ['key2', None]
    - key: 'key2'
    - value: None
  [2]:
    [True, 'key3', 'value3']
    - disabled: True
    - key: 'key3'
    - value: 'value3'
  [3]:
    ['key4', 'value4']
    - key: 'key4'
    - value: 'value4'
  [4]:
    ['subobject1', [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]]
    - attributes: [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]
      [0]:
        ['key1', 'value1']
        - key: 'key1'
        - value: 'value1'
      [1]:
        ['key2', 'value2']
        - key: 'key2'
        - value: 'value2'
      [2]:
        ['key3', 'value3']
        - key: 'key3'
        - value: 'value3'
    - name: 'subobject1'
  [5]:
    [True, 'key5', 'value5']
    - disabled: True
    - key: 'key5'
    - value: 'value5'
  [6]:
    ['key6', 'v_a_l_u_e_6']
    - key: 'key6'
    - value: 'v_a_l_u_e_6'
  [7]:
    ['subobject2', [['key1', 'value1']]]
    - attributes: [['key1', 'value1']]
      [0]:
        ['key1', 'value1']
        - key: 'key1'
        - value: 'value1'
    - name: 'subobject2'
  [8]:
    ['key7', 'value7']
    - key: 'key7'
    - value: 'value7'
  [9]:
    ['key8', None]
    - key: 'key8'
    - value: None
- name: 'object1'

РЕДАКТИРОВАТЬ: я удалил конструкции Dict, так как они действительно затрудняют обработку вывода.

0 голосов
/ 30 июня 2018

Это должно помочь вам. Подробности смотрите в комментариях.

from pyparsing import *

test_string ='''
object1 {
        key1 = value1
        key2
        #key3 = value3
        key4
        #key5 = value5
        key6 = value6
        subobject1 {
            key1 = value1
            key2 = value2
            key3 = value3
        }
}'''

# interpret inline 'string' as Suppress('string'), 
# instead of LBRACE,EQ,RBRACE,HASH = map(Suppress, '{=}#')
ParserElement.inlineLiteralsUsing(Suppress)  

# be sure to exclude special characters when using printables
name_expr = Word(printables, excludeChars='{}')
key_val_expr = '=' + Word(printables)

# p1('name') is equivalent to p1.setResultsName('name')
# p1 | p2 is equivalent to MatchFirst(p1, p2)
# if lineEnd() matches first, there is no value. 
# then use a parse action to return the string 'NONE' as value instead
# else, match a regular key_value
# also, you have to use Group because key_val_line is a repeating element
key_val_line = Group(name_expr('key') + (lineEnd().setParseAction(lambda t: 'NONE') | key_val_expr)('val'))
key_val_lines = OneOrMore(key_val_line)('key_val_lines')

obj = Forward()
obj << Group(name_expr('obj_name') + '{' + OneOrMore(key_val_lines | obj) + '}')('objects')

parse_results = obj.parseString(test_string)
print(parse_results.dump())

Это печатает следующее:

[['object1', ['key1', 'value1'], ['key2', 'NONE'], ['#key3', 'value3'], ['key4', 'NONE'], ['#key5', 'value5'], ['key6', 'value6'], ['subobject1', ['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]]]
- objects: ['object1', ['key1', 'value1'], ['key2', 'NONE'], ['#key3', 'value3'], ['key4', 'NONE'], ['#key5', 'value5'], ['key6', 'value6'], ['subobject1', ['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]]
  - key_val_lines: [['key1', 'value1'], ['key2', 'NONE'], ['#key3', 'value3'], ['key4', 'NONE'], ['#key5', 'value5'], ['key6', 'value6']]
    [0]:
      ['key1', 'value1']
      - key: 'key1'
      - val: 'value1'
    [1]:
      ['key2', 'NONE']
      - key: 'key2'
      - val: 'NONE'
    [2]:
      ['#key3', 'value3']
      - key: '#key3'
      - val: 'value3'
    [3]:
      ['key4', 'NONE']
      - key: 'key4'
      - val: 'NONE'
    [4]:
      ['#key5', 'value5']
      - key: '#key5'
      - val: 'value5'
    [5]:
      ['key6', 'value6']
      - key: 'key6'
      - val: 'value6'
  - obj_name: 'object1'
  - objects: ['subobject1', ['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]
    - key_val_lines: [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]
      [0]:
        ['key1', 'value1']
        - key: 'key1'
        - val: 'value1'
      [1]:
        ['key2', 'value2']
        - key: 'key2'
        - val: 'value2'
      [2]:
        ['key3', 'value3']
        - key: 'key3'
        - val: 'value3'
    - obj_name: 'subobject1'
...