首页 > 解决方案 > 如何处理来自解析 YAML 锚点的引用?

问题描述

我最近偶然发现了 php's 的一种行为parse_yaml,其中使用 YAML 中的锚引用定义的部分作为 PHP 数组中的引用返回,给出了这种行为:

$yaml = <<<YAML
a: &foo bar
b: *foo
YAML;

$arr = yaml_parse($yaml);
echo $arr["b"]; // returns "bar" as expected

// but when I update $arr["a"]:
$arr["a"] = "baz";
// $arr["b"] is also updated - because it's a reference!
echo $arr["b"]; // returns "baz"!

这很好,但现在对于我的应用程序,我需要展平这些引用,以便我可以单独更改这些值。

我对此确实有一个不好的解决方案,但是有一个好的解决方案吗?

这是我现在使用的不好的解决方案:

$yaml = <<<YAML
a: &foo bar
b: *foo
YAML;

$arr = yaml_parse(yaml_emit(yaml_parse($yaml))); // yaml_emit doesn't emit anchors/references
$arr["a"] = "baz";
echo $arr["b"]; // returns "bar"

标签: phparraysreferenceyaml

解决方案


如果您的输入在文件中test.yaml

a: &foo bar  # hello
b: *foo

然后使用以下程序加载和转储该文件,在 YAML 可以展开时展开它(即递归数据不能展平)。

import sys
from pathlib import Path
import ruamel.yaml

def null_op(*args, **kw):
     return True

# prevent anchors from being preserved even if there are no aliases for them
ruamel.yaml.comments.CommentedBase.yaml_set_anchor = null_op
ruamel.yaml.scalarstring.ScalarString.yaml_set_anchor = null_op
ruamel.yaml.scalarint.ScalarInt.yaml_set_anchor = null_op
ruamel.yaml.scalarfloat.ScalarFloat.yaml_set_anchor = null_op
ruamel.yaml.scalarbool.ScalarBoolean.yaml_set_anchor = null_op

# backup the original file if not backed up yet
yaml_file = Path('test.yaml')
backup = yaml_file.with_suffix('.yaml.org')
if not backup.exists():
    backup.write_bytes(yaml_file.read_bytes())

yaml = ruamel.yaml.YAML()
# yaml.indent(mapping=4, sequence=4, offset=2)
yaml.preserve_quotes = True
yaml.representer.ignore_aliases = null_op
data = yaml.load(yaml_file)
yaml.dump(data, yaml_file)

这使:

a: bar       # hello
b: bar       # hello

替换yaml_set_anchor方法是必要的,否则您的输出将在具有锚点别名的地方都有原始锚点。

如您所见,如果您对锚定数据有评论,则会将其复制(并保留原始起始列)。别名后的任何评论都会消失。这不会影响加载数据的语义,应该不是问题。


推荐阅读