如何处理来自解析YAML锚点的引用?

时间:2019-06-07 14:23:13

标签: php arrays reference yaml

我最近偶然发现了php的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"

1 个答案:

答案 0 :(得分:1)

如果您的输入在文件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方法,否则输出 在具有锚别名的地方都将具有原始锚。

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