【问题标题】:Parsing YAML, return with line number解析 YAML,返回行号
【发布时间】:2012-10-30 10:07:30
【问题描述】:

我正在用 YAML 数据制作一个文档生成器,它将指定每个项目是从 YAML 文件的哪一行生成的。做这个的最好方式是什么?所以如果 YAML 文件是这样的:

- key1: item 1
  key2: item 2
- key1: another item 1
  key2: another item 2

我想要这样的东西:

[
     {'__line__': 1, 'key1': 'item 1', 'key2': 'item 2'},
     {'__line__': 3, 'key1': 'another item 1', 'key2': 'another item 2'},
]

我目前正在使用 PyYAML,但如果我可以从 Python 中使用它,任何其他库都可以。

【问题讨论】:

  • 为了获得更多灵感,这是我的代码。它包含比上面要求的更多的信息,因为它在每个 dict/list/unicode 上使用 start_mark、end_mark 报告位置信息(分别使用 dict_node、list_node、unicode_node 子类)。 gist.github.com/dagss/5008118

标签: python parsing yaml pyyaml


【解决方案1】:

以下代码是基于以前的好答案,如果有人还需要定位叶子属性的行号,以下代码可能会有所帮助:

from yaml.composer import Composer
from yaml.constructor import Constructor
from yaml.nodes import ScalarNode
from yaml.resolver import BaseResolver
from yaml.loader import Loader


class LineLoader(Loader):
    def __init__(self, stream):
        super(LineLoader, self).__init__(stream)

    def compose_node(self, parent, index):
        # the line number where the previous token has ended (plus empty lines)
        line = self.line
        node = Composer.compose_node(self, parent, index)
        node.__line__ = line + 1
        return node

    def construct_mapping(self, node, deep=False):
        node_pair_lst = node.value
        node_pair_lst_for_appending = []

        for key_node, value_node in node_pair_lst:
            shadow_key_node = ScalarNode(tag=BaseResolver.DEFAULT_SCALAR_TAG, value='__line__' + key_node.value)
            shadow_value_node = ScalarNode(tag=BaseResolver.DEFAULT_SCALAR_TAG, value=key_node.__line__)
            node_pair_lst_for_appending.append((shadow_key_node, shadow_value_node))

        node.value = node_pair_lst + node_pair_lst_for_appending
        mapping = Constructor.construct_mapping(self, node, deep=deep)
        return mapping


if __name__ == '__main__':
    stream = """             # The first line
    key1:                    # This is the second line
      key1_1: item1
      key1_2: item1_2
      key1_3:
        - item1_3_1
        - item1_3_2
    key2: item 2
    key3: another item 1
    """
    loader = LineLoader(stream)
    data = loader.get_single_data()

    from pprint import pprint

    pprint(data)

输出如下,另外一个key前缀为“__line__”,比如同级的“__line__key”。

PS:对于列表项,我还不能显示行。

{'__line__key1': 2,
 '__line__key2': 8,
 '__line__key3': 9,
 'key1': {'__line__key1_1': 3,
          '__line__key1_2': 4,
          '__line__key1_3': 5,
          'key1_1': 'item1',
          'key1_2': 'item1_2',
          'key1_3': ['item1_3_1', 'item1_3_2']},
 'key2': 'item 2',
 'key3': 'another item 1'}

【讨论】:

  • 谢谢你。 __init__ 中的 self.compose_node = self.compose_node 实际上在这里做了什么吗?当我测试 sn-p 时,即使不包含 __init__ 函数,它似乎也能正常工作。
  • 谢谢,已经删除了。
【解决方案2】:

这是puzzlet's answer的改进版:

import yaml
from yaml.loader import SafeLoader

class SafeLineLoader(SafeLoader):
    def construct_mapping(self, node, deep=False):
        mapping = super(SafeLineLoader, self).construct_mapping(node, deep=deep)
        # Add 1 so line numbering starts at 1
        mapping['__line__'] = node.start_mark.line + 1
        return mapping

你可以这样使用它:

data = yaml.load(whatever, Loader=SafeLineLoader)

【讨论】:

  • 您没有提到,在不受控制的 YAML 输入上,这可能会导致您的磁盘被擦除(或更糟)。使用 OPs 示例输入,无需将其设置为不安全的,您可以只继承 SafeLoader 而不是 Loader。我也看不到这将如何解决在 OP(或任何其他)YAML 文档中获取序列的行号。
  • @Anthon 在当前版本的 PyYaml 中,LoaderSafeLoader 相同。
  • @Anthon 这是对puzzlet 答案的增强,具有相同的行为。它为 YAML 结构中的每个映射添加一个键 __line__,其值是该映射节点的起始行。
  • 我在 pyyaml 5.3.1 中得到了从 1 开始的行索引。
  • @V.Rubinetti 由于构造函数执行深度优先遍历,您可以添加一个属性来跟踪当前深度并覆盖construct_object() 以适当地增加/减少它。如果您的用例需要,您需要一些额外的逻辑来正确处理锚点。
【解决方案3】:

如果您使用ruamel.yaml >= 0.9(我是作者),并使用RoundTripLoader,您可以访问集合项上的属性lc,以获取它们在源 YAML:

def test_item_04(self):
    data = load("""
     # testing line and column based on SO
     # http://stackoverflow.com/questions/13319067/
     - key1: item 1
       key2: item 2
     - key3: another item 1
       key4: another item 2
        """)
    assert data[0].lc.line == 2
    assert data[0].lc.col == 2
    assert data[1].lc.line == 4
    assert data[1].lc.col == 2

(行和列从 0 开始计数)。

This answer 显示如何在加载过程中将lc 属性添加到字符串类型。

【讨论】:

  • 如果列表位于有序映射中,则可以找到一种方法让这项工作发挥作用,例如在 key1: !!omap\n - key4: item2\n - key3: item3 中,无法访问 key4key3 行号。跨度>
  • @zezollo 默认情况下,orderedmap 不会加载到 CommentedMap 结构中,因此没有 lc 属性。您必须将 !omap 加载注册为 CommentedMap 的子类。这是可行的,但我无法在评论中回答。如果您不知道该怎么做,您应该发布一个新问题。
  • 确实我想不通。我只找到了一个“肮脏”的解决方法来获取行号。提问here
【解决方案4】:

我通过向Composer.compose_nodeConstructor.construct_mapping 添加钩子来实现它:

import yaml
from yaml.composer import Composer
from yaml.constructor import Constructor

def main():
    loader = yaml.Loader(open('data.yml').read())
    def compose_node(parent, index):
        # the line number where the previous token has ended (plus empty lines)
        line = loader.line
        node = Composer.compose_node(loader, parent, index)
        node.__line__ = line + 1
        return node
    def construct_mapping(node, deep=False):
        mapping = Constructor.construct_mapping(loader, node, deep=deep)
        mapping['__line__'] = node.__line__
        return mapping
    loader.compose_node = compose_node
    loader.construct_mapping = construct_mapping
    data = loader.get_single_data()
    print(data)

【讨论】:

  • 谢谢 - 这非常有效,在错误报告方面非常有用。
  • 自 2006 年 9 月起,YAML 文件的推荐扩展名一直是 .yaml
猜你喜欢
  • 2021-06-06
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-06
  • 2018-01-07
相关资源
最近更新 更多