0

我有一个类似于下面的文本字符串:

statistics:
    time-started: Tue Feb  5 15:33:35 2013
    time-sampled: Thu Feb  7 12:25:39 2013
    statistic:
        active: 0
        interactive: 0
    count: 0
    up:
        packets: 0
        bytes: 0
    down:
        packets: 0
        bytes: 0

我需要解析上面的字符串(我需要解析的字符串实际上更大/更深,这里我只是提供了一个示例)。我认为解析一些元素的最简单方法是将此字符串转换为 XML 字符串并用于xml.etree.ElementTree选择我要查找的元素。

所以我想将上面的字符串转换成如下的 XML 字符串:

<statistics>
    <time-started>Tue Feb  5 15:33:35 2013</time-started>
    <time-sampled>Thu Feb  7 12:25:39 2013</time-sampled>
    <statistic>
        <active>0</active>
        <interactive>0</interactive>
    </statistic>
    <count>0</count>
    <up>
        <packets>0</packets>
        <bytes>0</bytes>
    </up>
    <down>
        <packets>0</packets>
        <bytes>0</bytes>
    </down>
</statistics>

如您所见,字符串中的所有信息都可用于将其转换为 XML。如果有简单的方法或模块可以做到这一点,我不想重新发明轮子。

4

2 回答 2

2

您基本上是在尝试将 YAML 转换为 XML。您可以使用 PyYAML 将输入字符串解析为 python dict,然后使用 xml 生成器将 dict 转换为 XML。

于 2013-02-07T12:51:05.090 回答
0

user2050283 绝对是对的,它是 yaml,这使得解析变得容易。主要是出于教育原因,我尝试自己解析它。期待一些反馈。

您的数据结构是分层的、树状的。所以让我们在 Python 中定义一棵树,尽可能简单(参考)

from collections import defaultdict

def tree(): return defaultdict(tree)

接下来,让我们在解析函数中使用这棵树。它遍历行,查看缩进,记录当前路径(也称为面包屑),并尝试将行拆分为键和值(如果存在)并填充我们的树。在适当的情况下,我将逻辑块提取为单独的函数,如下所示。如果缩进与之前的任何缩进都不匹配,则会引发错误——基本上就像 Python 对其源代码所做的那样。

def load_data(f):
    doc = tree()
    previous_indents = [""]
    path = [""]

    for line in map(lambda x: x.rstrip("\n"), 
                    filter( is_valid_line, f)
                ):
        line_wo_indent = line.lstrip(" ")
        indent = line[:(len(line) - len(line_wo_indent))]

        k, v = read_key_and_value(line_wo_indent)

        if len(indent) > len(previous_indents[-1]):
            previous_indents.append(indent)
            path.append(k)

        elif len(indent) == len(previous_indents[-1]):    
            path[-1] = k

        else: # indent is shorter
            try:
                while previous_indents[-1] != indent:
                    previous_indents.pop()
                    path.pop()            
            except IndexError:
                raise IndentationError("Indent doesn't match any previous indent.")
            path[-1] = k

        if v is not None:
            set_leaf_value_from_path(doc, path, v)
    return doc

我创建的辅助函数是:

  • set_leaf_value_from_path:采用树、路径(键列表)和值。它使用递归下降到树中并设置由路径定义的叶子的值。
  • read_key_and_value:将一行拆分为键和值,首先是“:”
  • is_valid_line:用于检查一行是否不为空或是否以数字符号开头

这是完整的脚本

from collections import defaultdict

def tree(): return defaultdict(tree)

def dicts(t): 
    if isinstance(t, dict):
        return {k: dicts(t[k]) for k in t}
    else:
        return t

def load_data(f):
    doc = tree()
    previous_indents = [""]
    path = [""]

    for line in map(lambda x: x.rstrip("\n"), 
                    filter( is_valid_line, f)
                ):
        line_wo_indent = line.lstrip(" ")
        indent = line[:(len(line) - len(line_wo_indent))]

        k, v = read_key_and_value(line_wo_indent)

        if len(indent) > len(previous_indents[-1]):
            previous_indents.append(indent)
            path.append(k)

        elif len(indent) == len(previous_indents[-1]):    
            path[-1] = k

        else: # indent is shorter
            try:
                while previous_indents[-1] != indent:
                    previous_indents.pop()
                    path.pop()            
            except IndexError:
                raise IndentationError("Indent doesn't match any previous indent.")
            path[-1] = k

        if v is not None:
            set_leaf_value_from_path(doc, path, v)
    return doc

def set_leaf_value_from_path(tree_, path, value):
    if len(path)==1:
        tree_[path[0]] = value
    else:
        set_leaf_value_from_path(tree_[path[0]], path[1:], value)

def read_key_and_value(line):
    pos_of_first_column = line.index(":")
    k = line[:pos_of_first_column].strip()
    v = line[pos_of_first_column+1:].strip()
    return k, v if len(v) > 0 else None

def is_valid_line(line):
    if line.strip() == "":
        return False
    if line.lstrip().startswith("#"):
        return False
    return True


if __name__ == "__main__":
    import cStringIO

    document_str = """
statistics:
    time-started: Tue Feb  5 15:33:35 2013
    time-sampled: Thu Feb  7 12:25:39 2013
    statistic:
        active: 0
        interactive: 0
    count: 1
    up:
        packets: 2
        bytes: 2
    down:
        packets: 3
        bytes: 3
"""
    f = cStringIO.StringIO(document_str)
    doc = load_data(f)

    from pprint import pprint
    pprint(dicts(doc))

已知限制:

  • 仅支持标量作为值
  • 只有字符串标量作为值
  • 不支持多行标量
  • 注释没有像定义中那样实现,也就是说,它们可能不在一行的任何地方开始;只有以数字符号开头的行被视为注释

这些只是已知的限制。我敢肯定 YAML 的其他部分也不支持。但这似乎足以满足您的数据。

于 2013-02-07T14:52:13.177 回答