在Python中解析未知数据结构

3 投票
6 回答
2845 浏览
提问于 2025-04-15 12:31

我有一个文件,里面有很多数据,格式大概是这样的:

Group1 {  
    Entry1 {  
        Title1 [{Data1:Member1, Data2:Member2}]  
        Title2 [{Data3:Member3, Data4:Member4}]  
    }  
    Entry2 {  
        ...  
    }  
}
Group2 {
    DifferentEntry1 {
        DiffTitle1 {
            ...
        }
    }
}

问题是,我不知道里面有多少层括号,也不清楚数据是怎么组织的。我需要修改这些数据,并根据一些条件删除整个“条目”,然后再把所有内容写入一个新文件。请问,读取这样的文件最好的方法是什么?谢谢!

6 个回答

3

这里有一个语法规则。

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

我对在列表中嵌套的字典和列表后面的逗号有点不确定,因为你没有提供相关的例子。

3

这个数据结构基本上看起来像是一个字典,字典里的键是字符串,而值可以是一个字符串或者另一个同样类型的字典。所以我建议可以把它转化成这种Python的结构。

比如:

{'group1': {'Entry2': {}, 'Entry1': {'Title1':{'Data4': 'Member4',
'Data1': 'Member1','Data3': 'Member3', 'Data2': 'Member2'}, 
'Title2': {}}}

在文件的最上面,你可以先创建一个空字典。然后对于你读取的每一行,使用标识符作为键。当你看到一个 { 的时候,就为这个键创建一个字典作为值。当你看到 Key:Value 的时候,就不需要再创建一个字典,而是直接把值放进去。当你看到一个 } 的时候,你需要“回到”之前正在处理的字典,继续填充它。

我觉得这个解析器可以用一个相对简短的递归函数来完成。这个函数会在看到 { 的时候调用自己来填充每个子字典,然后在看到 } 的时候返回到上一级。

1

我有一个类似的东西,但它是用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();
}

撰写回答