在Python中解析未知数据结构
我有一个文件,里面有很多数据,格式大概是这样的:
Group1 {
Entry1 {
Title1 [{Data1:Member1, Data2:Member2}]
Title2 [{Data3:Member3, Data4:Member4}]
}
Entry2 {
...
}
}
Group2 {
DifferentEntry1 {
DiffTitle1 {
...
}
}
}
问题是,我不知道里面有多少层括号,也不清楚数据是怎么组织的。我需要修改这些数据,并根据一些条件删除整个“条目”,然后再把所有内容写入一个新文件。请问,读取这样的文件最好的方法是什么?谢谢!
6 个回答
这里有一个语法规则。
dict_content : NAME ':' NAME [ ',' dict_content ]?
| NAME '{' [ dict_content ]? '}' [ dict_content ]?
| NAME '[' [ list_content ]? ']' [ dict_content ]?
;
list_content : NAME [ ',' list_content ]?
| '{' [ dict_content ]? '}' [ ',' list_content ]?
| '[' [ list_content ]? ']' [ ',' list_content ]?
;
最顶层的内容是 dict_content
。
我对在列表中嵌套的字典和列表后面的逗号有点不确定,因为你没有提供相关的例子。
这个数据结构基本上看起来像是一个字典,字典里的键是字符串,而值可以是一个字符串或者另一个同样类型的字典。所以我建议可以把它转化成这种Python的结构。
比如:
{'group1': {'Entry2': {}, 'Entry1': {'Title1':{'Data4': 'Member4',
'Data1': 'Member1','Data3': 'Member3', 'Data2': 'Member2'},
'Title2': {}}}
在文件的最上面,你可以先创建一个空字典。然后对于你读取的每一行,使用标识符作为键。当你看到一个 { 的时候,就为这个键创建一个字典作为值。当你看到 Key:Value 的时候,就不需要再创建一个字典,而是直接把值放进去。当你看到一个 } 的时候,你需要“回到”之前正在处理的字典,继续填充它。
我觉得这个解析器可以用一个相对简短的递归函数来完成。这个函数会在看到 { 的时候调用自己来填充每个子字典,然后在看到 } 的时候返回到上一级。
我有一个类似的东西,但它是用Java写的。它解析一个结构基本相同但语法稍有不同的文件(没有'{'和'}',只有像Python那样的缩进)。这是一种非常简单的脚本语言。
基本上,它的工作原理是这样的:它使用一个栈来跟踪最内层的指令块(或者在你的情况下是数据),并将每条新指令添加到栈顶的块中。如果它解析到一条需要新块的指令,就会把这个块推入栈中。如果一个块结束了,就会从栈中弹出一个元素。
我不想发布整个源代码,因为它很大,而且可以在谷歌代码上找到(lizzard-entertainment,修订版405)。不过有几件事情你需要知道。
- 指令是一个抽象类,它有一个block_expected方法,用来指示具体的指令是否需要一个块(比如循环等)。在你的情况下,这个方法是不必要的,你只需要检查'{'。
- 块是指令的一个扩展。它包含一个指令列表,并有一个add方法来添加更多指令。
- indent_level返回指令文本前面有多少个空格。对于使用'{}'的情况,这个也是不必要的。
placeholder
BufferedReader input = null;
try {
input = new BufferedReader(new FileReader(inputFileName));
// Stack of instruction blocks
Stack<Block> stack = new Stack<Block>();
// Push the root block
stack.push(this.topLevelBlock);
String line = null;
Instruction prev = new Noop();
while ((line = input.readLine()) != null) {
// Difference between the indentation of the previous and this line
// You do not need this you will be using {} to specify block boundaries
int level = indent_level(line) - stack.size();
// Parse the line (returns an instruction object)
Instruction inst = Instruction.parse(line.trim().split(" +"));
// If the previous instruction expects a block (for example repeat)
if (prev.block_expected()) {
if (level != 1) {
// TODO handle error
continue;
}
// Push the previous instruction and add the current instruction
stack.push((Block)(prev));
stack.peek().add(inst);
} else {
if (level > 0) {
// TODO handle error
continue;
} else if (level < 0) {
// Pop the stack at the end of blocks
for (int i = 0; i < -level; ++i)
stack.pop();
}
stack.peek().add(inst);
}
prev = inst;
}
} finally {
if (input != null)
input.close();
}