Разбор YAML, возврат с номером строки
Я делаю генератор документов из данных 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.
5 ответов
Вот улучшенная версия ответа головоломки:
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)
Я сделал это, добавив хуки Composer.compose_node
а также Constructor.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)
Если вы используете ruamel.yaml > = 0,9 (автором которого я являюсь), и используйте RoundTripLoader
, вы можете получить доступ к собственности lc
на элементах коллекции, чтобы получить строку и столбец, где они начинались в исходном YAML:
def test_item_04(self):
data = load("""
# testing line and column based on SO
# http://stackru.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).
Этот ответ показывает, как добавить lc
приписывать строковые типы во время загрузки.
Следующие коды основаны на предыдущих хороших ответах, если кому-то также нужно найти номера строк атрибутов листьев , могут помочь следующие коды:
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)
И вывод следующий, с другим ключом с префиксом «__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'}
Для дальнейшего вдохновения вот мой код для этого. Он содержит больше информации, чем запрошено выше, так как он сообщает информацию о местоположении, используя start_mark, end_mark для каждого подкласса dict/list/unicode (используя подклассы dict_node, list_node, unicode_node, соответственно).