【问题标题】:How do I read/write markdown yaml frontmatter with ruamel.yaml?如何使用 ruamel.yaml 读/写 markdown yaml frontmatter?
【发布时间】:2021-08-30 00:14:56
【问题描述】:

我想使用 Python 在 markdown 文件中读写 YAML frontmatter。我遇到了 ruamel.yaml 包,但无法理解如何将其用于此目的。

如果我有降价文件:

---
car: 
  make: Toyota
  model: Camry
---

# My Ultimate Car Review
This is a good car.

首先,有没有办法在我的 python 代码中将 yaml 数据设置为变量?

第二,有没有办法给markdown文件中的yaml设置新的值?

第一个,我试过了:

from ruamel.yaml import YAML
import sys

f = open("cars.txt", "r+") # I'm really not sure if r+ is ideal here.

yaml = YAML()
code = yaml.load(f)
print(code['car']['make'])

但得到一个错误:

ruamel.yaml.composer.ComposerError: expected a single document in the stream
  in "cars.txt", line 2, column 1
but found another document
  in "cars.txt", line 5, column 1

第二个,我试过了:

from ruamel.yaml import YAML
import sys

f = open("cars.txt", "r+") # I'm really not sure if r+ is ideal here.

yaml = YAML()
code = yaml.load(f)
code['car']['model'] = 'Sequoia'

但得到同样的错误错误:

ruamel.yaml.composer.ComposerError: expected a single document in the stream
  in "cars.txt", line 2, column 1
but found another document
  in "cars.txt", line 5, column 1

【问题讨论】:

  • ruamel.yaml 的作者指出here 他/她的包可以用于带有 yaml frontmatter 的文件。但是,给出的示例太复杂,我无法解析。我希望包开发者或其他人能给出一个更简单的例子。
  • 这个question 会有所帮助,你必须要两个文件,你必须使用load_all

标签: python yaml ruamel.yaml


【解决方案1】:

当您在一个文件中有多个 YAML 文档时,这些文档用一行分隔,包括 三个破折号,或以三个破折号开头,后跟一个空格。 大多数 YAML 解析器,包括 ruamel.yaml 都需要一个文档文件(使用 YAML().load() 时) 或多文档文件(使用 YAML().load_all() 时)。

方法.load()返回单个数据结构,如果似乎不止一个则报错 文件(即当它遇到文件中的第二个--- 时)。这 .load_all() 方法可以处理一个或多个 YAML 文档,但总是返回 一个迭代器。

您的输入恰好是一个有效的多文档 YAML 文件,但降价部分通常使情况并非如此。它很容易 通过将第二个 --- 更改为 --- | 始终是有效的 YAML,从而使 降价部分(多行)文字标量字符串。我不知道为什么 这种 YAML frontmatter 格式的设计者没有指定,它可能必须 这样做一些解析器(如 PyYAML)无法解析这种非缩进的文字标量 根级别的字符串正确,尽管这些示例在 YAML 中 规范。

在您的示例中,降价部分非常简单,以至于它是有效的 YAML,没有 必须为文字标量字符串指定 |。所以你可以使用 .load_all() 在这个输入上。但只是添加例如一条线 从降价部分的破折号开始,将导致无效的 YAML 文档,所以如果你使用.load_all(),你必须确保你 不要迭代到解析第二个文档:

import sys
from pathlib import Path
import ruamel.yaml

path = Path('cars.txt')

yaml = ruamel.yaml.YAML()
for data in yaml.load_all(path):
    break
print(data['car']['make'])

给出:

Toyota

但是,您不应该尝试更新文件(所以不要使用r+),因为您的 YAML 前线可能是 比原来的更长,并且更新会覆盖您的降价。为了 更新,将文件读入内存,根据第二行分成两部分 破折号,更新数据,转储并附加破折号和降价:

import sys
from pathlib import Path
import ruamel.yaml

path = Path('cars.txt')
opath = Path('cars_out.txt')
yaml_str, markdown = path.read_text().lstrip().split('\n---', 1)
yaml_str += '\n' # re-add the trailing newline that was split off

yaml = ruamel.yaml.YAML()
yaml.explicit_start = True
data = yaml.load(yaml_str)

data['car']['year'] = 2003

with opath.open('w') as fp:
    yaml.dump(data, fp)
    fp.write('---')
    fp.write(markdown)

sys.stdout.write(opath.read_text())

给出:

---
car:
  make: Toyota
  model: Camry
  year: 2003
---

# My Ultimate Car Review
This is a good car.

【讨论】:

  • 顺便说一句,完全可以调整解析器,使其在第二个 --- 之后自动插入文字标量的标记。但是由于------ | 都可能出现在markdown 中,从而导致两个以上的文档,因此尝试从解析的YAML 中写回这样的markdown 部分会很麻烦。
  • 感谢您的详细解答。这行得通。我必须在yaml.dump(data, fp) 之前插入fp.write('---\n') 以在输出文件中保留初始---。我打算将输出直接写回输入文件。在这种情况下它似乎有效,但是在这样做时要注意什么问题?谢谢!
  • 我忘记了初始文档开始指示符,ruamel.yaml 没有保留它(它在单个 doc YAML 文件中是多余的)。我使用.explicit_start 属性更新了答案。你可以毫无问题地做opath = path。一切都被读入内存,您可以覆盖输出。我在测试期间不这样做,因为我每次都必须重建输入文件。
  • 如果您的文件很大,使用read_bytes.open('wb') 并调整其余部分以使用字节而不是转换为/从Unicode 转换会更快/更好。
猜你喜欢
  • 2015-03-29
  • 1970-01-01
  • 2016-08-25
  • 2019-01-23
  • 1970-01-01
  • 2021-12-24
  • 2021-11-21
  • 2022-11-07
  • 2015-09-25
相关资源
最近更新 更多