JAVA合并YAML文件,并保留注释

611 阅读3分钟

项目上需求,需要把多个yaml合并,并保留注释,下面是我实现的代码,能力有限,望指导

依赖

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>你的版本</version>
</dependency>

实现代码

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 合并yaml
 *
 * @author Minhat
 */
public final class YamlUtils {
    /**
     * 行内注释
     */
    private static final String YAML_SUFFIX_COMMENT = " #";

    /**
     * 数组前缀
     */
    private static final String YAML_ARRAY_SUFFIX = "- ";

    /**
     * yaml可以分割
     */
    private static final String YAML_KEY_SPLIT = ":";

    /**
     * 换行
     */
    private static final String REGEX_ENTER = "\n";

    /**
     * 匹配值只包含制表符和空格
     */
    private static final String REGEX_TABLE_SPACE = ".*\S.*";

    /**
     * 整行注释
     */
    private static final String REGEX_ROW_COMMENT = "^\s*#";

    /**
     * 数组注释
     */
    private static final String REGEX_ROW_ARRAY_COMMENT = "^\s*-";

    /**
     * YamlKey
     */
    private static final String REGEX_YAML_KEY = "(\w+):";

    /**
     * YamlKey级别
     */
    private static final String REGEX_YAML_KEY_LEVEL = "(\s*)(\S+)";

    /**
     * 合并文件,,并保留注释
     *
     * @param yamls yaml文件
     * @return 返回合并后的yaml
     */
    public static String mergedYaml(String... yamls) {
        Map<String, Object> merged = new LinkedHashMap<>();
        Map<CommentKey, List<CommentValue>> commentMap = new LinkedHashMap<>();
        for (String yaml : yamls) {
            commentMap.putAll(readYaml(yaml));
            merged.putAll(new Yaml().load(yaml));
        }

        // 设置输出格式选项,包括空格和注释
        DumperOptions options = new DumperOptions();
        options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        options.setPrettyFlow(true);
        options.setIndent(2);
        options.setWidth(120);
        options.setProcessComments(true);
        String dump = new Yaml(options).dump(merged);
        return mergedComment(commentMap, dump);
    }

    /**
     * 合并yaml和注释
     *
     * @param commentMap  注释map
     * @param yamlContext yaml内容
     * @return 返回合并后的yuml
     */
    private static String mergedComment(Map<CommentKey, List<CommentValue>> commentMap, String yamlContext) {
        // 源数据
        StringBuilder sourceData = new StringBuilder();
        String[] yamlArray = yamlContext.split(REGEX_ENTER);

        String allKey = "";
        for (String rowYaml : yamlArray) {
            // 正则前面有多少空格,也就是key层级
            Pattern r = Pattern.compile(REGEX_YAML_KEY_LEVEL);
            Matcher m = r.matcher(rowYaml);
            int level = 0;
            if (m.find()) {
                level = (m.group(1).length() + 1) / 2;
            }

            if (level == 0) {
                allKey = "";
            }

            // 获取yaml的key
            Pattern pattern = Pattern.compile(REGEX_YAML_KEY);
            Matcher matcher = pattern.matcher(rowYaml);

            String rowKey = "";
            // 判断是否是普通key还是数组元素
            if (matcher.find()) {
                rowKey = matcher.group(1);
                if (StringUtils.isNotBlank(allKey)) {
                    allKey = allKey + YAML_KEY_SPLIT + rowKey;
                } else {
                    allKey = rowKey;
                }
                rowKey = allKey;
            }

            // 获取yaml的数组key
            Pattern pattern2 = Pattern.compile(REGEX_ROW_ARRAY_COMMENT);
            Matcher matcher2 = pattern2.matcher(rowYaml);
            if (matcher2.find()) {
                rowKey = allKey + YAML_KEY_SPLIT + rowYaml.substring(rowYaml.indexOf(YAML_ARRAY_SUFFIX) + 2);
            }
            List<CommentValue> commentValues = new ArrayList<>();
            for (CommentKey commentKey : commentMap.keySet()) {
                if (commentKey.allKey.equals(rowKey)) {
                    commentValues.addAll(commentMap.get(commentKey));
                }
            }

            // 上
            StringBuilder up = new StringBuilder();
            // 后
            StringBuilder suffix = new StringBuilder();
            // 下
            StringBuilder sourceDown = new StringBuilder();
            for (CommentValue commentValue : commentValues) {
                if (commentValue.comment.isEmpty()) {
                    continue;
                }
                if (LocationEnum.UP.equals(commentValue.getLocation())) {
                    up.append(commentValue.comment).append("\n");
                }
                if (LocationEnum.SUFFIX.equals(commentValue.getLocation())) {
                    suffix.append(commentValue.comment);
                }
                if (LocationEnum.DOWN.equals(commentValue.getLocation())) {
                    sourceDown.append(commentValue.comment).append("\n");
                }
            }
            if (!up.isEmpty()) {
                sourceData.append(up);
            }
            sourceData.append(rowYaml);
            if (!suffix.isEmpty()) {
                sourceData.append(suffix);
            }
            sourceData.append("\n");
            if (!sourceDown.isEmpty()) {
                sourceData.append(sourceDown);
            }
        }
        return sourceData.toString();
    }

    /**
     * 读取Yaml
     *
     * @param yamlContext Yaml内容
     * @return 返回封装
     */
    private static Map<CommentKey, List<CommentValue>> readYaml(String yamlContext) {
        // 注释key信息
        List<CommentKey> commentKeys = new ArrayList<>();
        // 注释列表
        List<CommentValue> comments = new ArrayList<>();
        // 注释Map
        Map<CommentKey, List<CommentValue>> commentMap = new LinkedHashMap<>();

        // 按换行符拆分
        String[] yamlArray = yamlContext.split(REGEX_ENTER);
        for (String rowYaml : yamlArray) {
            // 过滤空行
            // 正则表达式检查是否有非空格字符
            if (!rowYaml.matches(REGEX_TABLE_SPACE)) {
                continue;
            }
            Pattern rowCommentPattern = Pattern.compile(REGEX_ROW_COMMENT);
            Matcher rowCommentMatcher = rowCommentPattern.matcher(rowYaml);
            // 匹配是否是一整行注释
            if (rowCommentMatcher.find()) {
                comments.add(CommentValue.buildUp(rowYaml));
            } else {
                // 如果不是整行注释,判断是否有行内注释
                // TODO 可以优化成把前面所有的空格都分割下来
                if (rowYaml.contains(YAML_SUFFIX_COMMENT)) {
                    comments.add(CommentValue.buildSuffix(rowYaml.substring(rowYaml.indexOf(YAML_SUFFIX_COMMENT))));
                }

                // 正则前面有多少空格,也就是key层级
                Pattern r = Pattern.compile(REGEX_YAML_KEY_LEVEL);
                Matcher m = r.matcher(rowYaml);
                int level = 0;
                if (m.find()) {
                    level = (m.group(1).length() + 1) / 2;
                }

                // 获取yaml的key
                Pattern pattern = Pattern.compile(REGEX_YAML_KEY);
                Matcher matcher = pattern.matcher(rowYaml);

                // 判断是否是普通key还是数组元素
                if (matcher.find()) {
                    String rowKey = matcher.group(1);
                    CommentKey commentKey = CommentKey.build(level, rowKey, commentKeys);
                    commentKeys.add(commentKey);
                    commentMap.put(commentKey, comments);
                }
                // 获取yaml的数组key
                Pattern pattern2 = Pattern.compile(REGEX_ROW_ARRAY_COMMENT);
                Matcher matcher2 = pattern2.matcher(rowYaml);
                if (matcher2.find()) {
                    // 获取key
                    String rowKey = rowYaml.substring(rowYaml.indexOf(YAML_ARRAY_SUFFIX) + 2, rowYaml.indexOf(YAML_SUFFIX_COMMENT));
                    CommentKey commentKey = CommentKey.build(level, rowKey, commentKeys);
                    commentKeys.add(commentKey);
                    commentMap.put(commentKey, comments);
                }
                comments = new ArrayList<>();
            }
        }
        return commentMap;
    }

    /**
     * 注释key
     */
    @Data
    private static class CommentKey {
        /**
         * 层级级别
         */
        private int level;
        /**
         * 当前key
         */
        private String key;
        /**
         * 全key
         */
        private String allKey;

        private CommentKey() {
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            CommentKey that = (CommentKey) o;
            return level == that.level && Objects.equals(key, that.key) && Objects.equals(allKey, that.allKey);
        }

        /**
         * 这里重新Hashcode,原因是,lombok的@data注解,会把两个不一样的对象,但是内部值一样的对象的hashcode计算成一样的
         *
         * @return 返回hashCode
         */
        @Override
        public int hashCode() {
            return super.hashCode();
        }

        public static CommentKey build(int level, String key, List<CommentKey> commentKeys) {
            CommentKey commentKey = new CommentKey();
            commentKey.level = level;
            commentKey.key = key;
            commentKey.allKey = key;

            // 如果是一级结束
            if (commentKey.level == 0) {
                return commentKey;
            }
            // 如果list是空,结束
            if (commentKeys.isEmpty()) {
                return commentKey;
            }
            for (int i = commentKeys.size() - 1; i >= 0; i--) {
                CommentKey lastKey = commentKeys.get(i);
                if (lastKey.level == commentKey.level - 1) {
                    commentKey.allKey = lastKey.allKey + YAML_KEY_SPLIT + commentKey.allKey;
                    break;
                }
            }
            return commentKey;
        }
    }

    /**
     * 注释值
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class CommentValue {
        /**
         * 位置
         */
        private LocationEnum location;
        /**
         * 注释
         */
        private String comment;

        public static CommentValue buildUp(String comment) {
            return new CommentValue(LocationEnum.UP, comment);
        }

        public static CommentValue buildSuffix(String comment) {
            return new CommentValue(LocationEnum.SUFFIX, comment);
        }

        public static CommentValue buildDown(String comment) {
            return new CommentValue(LocationEnum.DOWN, comment);
        }
    }

    /**
     * 方位
     */
    private enum LocationEnum {
        /**
         * 上
         */
        UP,
        /**
         * 后追加
         */
        SUFFIX,
        /**
         * 下
         */
        DOWN,
    }
}

使用方法

String yaml1Context = "";
String yaml2Context = "";
String yamlContext = YamlUtils.mergedYaml(yaml1Context, yaml2Context);
System.out.println(yamlContext);