我有两个YAML文件,“A”和“B”,我希望将A的内容插入到B中,要么拼接到现有的数据结构中,就像数组一样,要么作为元素的子元素,就像某个散列键的值一样。

这可能吗?怎么做?如果不是,是否有指向规范引用的指针?


当前回答

标准YAML 1.2本身不包括这个特性。尽管如此,许多实现提供了一些扩展来实现这一点。

我给出了一种使用Java和snakeyaml:1.24(用于解析/发出YAML文件的Java库)来实现它的方法,它允许创建一个自定义YAML标记来实现以下目标(你会看到我用它来加载在几个YAML文件中定义的测试套件,并且我让它作为一个目标测试:节点的包含列表工作):

# ... yaml prev stuff

tests: !include
  - '1.hello-test-suite.yaml'
  - '3.foo-test-suite.yaml'
  - '2.bar-test-suite.yaml'

# ... more yaml document

下面是允许处理!include标记的单类Java。文件从classpath (Maven资源目录)加载:

/**
 * Custom YAML loader. It adds support to the custom !include tag which allows splitting a YAML file across several
 * files for a better organization of YAML tests.
 */
@Slf4j   // <-- This is a Lombok annotation to auto-generate logger
public class MyYamlLoader {

    private static final Constructor CUSTOM_CONSTRUCTOR = new MyYamlConstructor();

    private MyYamlLoader() {
    }

    /**
     * Parse the only YAML document in a stream and produce the Java Map. It provides support for the custom !include
     * YAML tag to split YAML contents across several files.
     */
    public static Map<String, Object> load(InputStream inputStream) {
        return new Yaml(CUSTOM_CONSTRUCTOR)
                .load(inputStream);
    }


    /**
     * Custom SnakeYAML constructor that registers custom tags.
     */
    private static class MyYamlConstructor extends Constructor {

        private static final String TAG_INCLUDE = "!include";

        MyYamlConstructor() {
            // Register custom tags
            yamlConstructors.put(new Tag(TAG_INCLUDE), new IncludeConstruct());
        }

        /**
         * The actual include tag construct.
         */
        private static class IncludeConstruct implements Construct {

            @Override
            public Object construct(Node node) {
                List<Node> inclusions = castToSequenceNode(node);
                return parseInclusions(inclusions);
            }

            @Override
            public void construct2ndStep(Node node, Object object) {
                // do nothing
            }

            private List<Node> castToSequenceNode(Node node) {
                try {
                    return ((SequenceNode) node).getValue();

                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(String.format("The !import value must be a sequence node, but " +
                            "'%s' found.", node));
                }
            }

            private Object parseInclusions(List<Node> inclusions) {

                List<InputStream> inputStreams = inputStreams(inclusions);

                try (final SequenceInputStream sequencedInputStream =
                             new SequenceInputStream(Collections.enumeration(inputStreams))) {

                    return new Yaml(CUSTOM_CONSTRUCTOR)
                            .load(sequencedInputStream);

                } catch (IOException e) {
                    log.error("Error closing the stream.", e);
                    return null;
                }
            }

            private List<InputStream> inputStreams(List<Node> scalarNodes) {
                return scalarNodes.stream()
                        .map(this::inputStream)
                        .collect(toList());
            }

            private InputStream inputStream(Node scalarNode) {
                String filePath = castToScalarNode(scalarNode).getValue();
                final InputStream is = getClass().getClassLoader().getResourceAsStream(filePath);
                Assert.notNull(is, String.format("Resource file %s not found.", filePath));
                return is;
            }

            private ScalarNode castToScalarNode(Node scalarNode) {
                try {
                    return ((ScalarNode) scalarNode);

                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(String.format("The value must be a scalar node, but '%s' found" +
                            ".", scalarNode));
                }
            }
        }

    }

}

其他回答

据我所知,YAML中不直接支持include,你必须自己提供一种机制,然而,这通常很容易做到。

我在我的python应用程序中使用YAML作为配置语言,在这种情况下经常定义这样的约定:

>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]

然后在我的(python)代码中:

import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
   cfg.update(yaml.load(open(inc)))

唯一的缺点是include中的变量将总是覆盖main中的变量,并且没有办法通过改变“includes:”语句在main中出现的位置来改变优先级。yml文件。

稍微不同的一点是,YAML不支持include,因为它并不是像基于文件的标记那样专门设计的。如果你在AJAX请求的响应中得到一个包含,它意味着什么?

对于Python用户,可以尝试pyyaml-include。

安装

pip install pyyaml-include

使用

import yaml
from yamlinclude import YamlIncludeConstructor

YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir='/your/conf/dir')

with open('0.yaml') as f:
    data = yaml.load(f, Loader=yaml.FullLoader)

print(data)

假设我们有这样的YAML文件:

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml

1.Yaml的内容:

name: "1"

2.Yaml的内容:

name: "2"

按名称包含文件

顶层: 如果0。yaml是:

!include include.d/1.yaml

我们会得到:

{"name": "1"}

在映射: 如果0。yaml是:

file1: !include include.d/1.yaml
file2: !include include.d/2.yaml

我们会得到:

  file1:
    name: "1"
  file2:
    name: "2"

在序列: 如果0。yaml是:

files:
  - !include include.d/1.yaml
  - !include include.d/2.yaml

我们会得到:

files:
  - name: "1"
  - name: "2"

ℹ注意: 文件名可以是绝对的(如/usr/conf/1.5/ make .yml)或相对的(如../../cfg/img.yml)。

通过通配符包含文件

文件名可以包含shell样式的通配符。从通配符找到的文件中加载的数据将按顺序设置。

如果0。yaml是:

files: !include include.d/*.yaml

我们会得到:

files:
  - name: "1"
  - name: "2"

ℹ注意: 对于Python>=3.5,如果递归参数!include YAML标记为真,模式" ** "将匹配任何文件和零个或多个目录和子目录。 在大型目录树中使用“**”模式可能会因为递归搜索而消耗过多的时间。

为了启用递归参数,我们应该在映射或序列模式下编写!include标记:

序列模式参数:

!include [tests/data/include.d/**/*.yaml, true]

映射模式参数说明

!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

加上上面@Joshbode的初始回答,我对代码片段进行了一些修改,以支持UNIX风格的通配符模式。

不过我还没有在windows中进行测试。为了便于维护,我面临着将大型yaml中的数组拆分到多个文件中的问题,并正在寻找一种解决方案,以便在基本yaml的同一个数组中引用多个文件。因此,下面的解决方案。解决方案不支持递归引用。它只支持在基本yaml中引用的给定目录级别中的通配符。

import yaml
import os
import glob


# Base code taken from below link :-
# Ref:https://stackoverflow.com/a/9577670
class Loader(yaml.SafeLoader):

    def __init__(self, stream):

        self._root = os.path.split(stream.name)[0]

        super(Loader, self).__init__(stream)

    def include(self, node):
        consolidated_result = None
        filename = os.path.join(self._root, self.construct_scalar(node))

        # Below section is modified for supporting UNIX wildcard patterns
        filenames = glob.glob(filename)
        
        # Just to ensure the order of files considered are predictable 
        # and easy to debug in case of errors.
        filenames.sort()
        for file in filenames:
            with open(file, 'r') as f:
                result = yaml.load(f, Loader)

            if isinstance(result, list):
                if not isinstance(consolidated_result, list):
                    consolidated_result = []
                consolidated_result += result
            elif isinstance(result, dict):
                if not isinstance(consolidated_result, dict):
                    consolidated_result = {}
                consolidated_result.update(result)
            else:
                consolidated_result = result

        return consolidated_result


Loader.add_constructor('!include', Loader.include)

使用

a:
  !include a.yaml

b:
  # All yamls included within b folder level will be consolidated
  !include b/*.yaml

不幸的是,YAML在其标准中没有提供这个功能。

但是如果你正在使用Ruby,有一个gem通过扩展Ruby YAML库来提供你所要求的功能: https://github.com/entwanderer/yaml_extend

根据之前的帖子:

  class SimYamlLoader(yaml.SafeLoader):
        '''
        Simple custom yaml loader that supports include, e.g:

        main.yaml:

        - !include file1.yaml
        - !include dir/file2.yaml

        '''

        def __init__(self, stream):
            self.root = os.path.split(stream.name)[0]
            super().__init__(stream)

    def _include(loader, node):
        filename = os.path.join(loader.root, loader.construct_scalar(node))
        with open(filename, 'r') as f:
            return yaml.load(f, SimYamlLoader)
    SimYamlLoader.add_constructor('!include', _include)

    # example:
    with open('main.yaml', 'r') as f:
        lists = yaml.load(f, SimYamlLoader)
        # if you want to merge the lists
        data = functools.reduce(
            lambda x, y: x if y is None else {**x, **dict(y)}, lists, {})
        # python 3.10+:lambda x, y: x if y is None else x | dict(y), lists, {})