2

我需要解析一个文件,其中包含用大括号分隔的信息,例如:

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 更好吗?

4

2 回答 2

5

嵌套表达式非常常见,如果您不使用解析库,通常需要递归解析器定义或递归代码。这段代码对于初学者来说可能会让人望而生畏,即使对于专家来说也容易出错,所以这就是我在nestedExprpyparsing 中添加帮助程序的原因。

您遇到的问题是您的输入字符串不仅仅是一个嵌套的大括号表达式。当我第一次尝试解析器时,我尽量保持测试尽可能简单——例如,我内联示例而不是从文件中读取它。

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 是在前导词“Continent”上磕磕绊绊,因为这个词不是大括号中嵌套表达式的开头,pyparsing(正如我们在异常信息中看到的) ) 正在寻找一个开头的“{”。

解决方案是稍微修改解析器以处理介绍性“大陆”标签,方法是将 expr 更改为:

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

现在,将结果打印为列表(使用 OP 中完成的 pprint,干得好)看起来像:

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

这应该与您的大括号嵌套相匹配。

于 2013-06-06T18:17:17.920 回答
4

递归是这里的关键。尝试一下:

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')])])])]

请将此作为唯一起点,并随时根据需要改进代码(根据您的数据,字典可能是更好的选择)。此外,示例代码不能正确处理格式错误的数据(特别是额外或缺失的数据}——我敦促您进行完整的测试覆盖;)


编辑: Discovering 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']]]]]
于 2013-06-06T10:21:51.980 回答