Java snakeyaml 修改yaml文件保留注释工具类封装

最近需要写一个Java代码用来批量修改yaml文件,然后搜了下,发现有一个 snakeyaml 的工具包可以使用。使用的话也比较简单:

    // 读取File f = new File("xxx.yaml");String s = FileUtils.readFileToString(f, StandardCharsets.UTF_8);Yaml yaml = new Yaml();LinkedHashMap map = yaml.loadAs(s, LinkedHashMap.class);map.put("key", "value");// 写入文件s = yaml.dump(map);FileUtils.writeStringToFile(f, s, StandardCharsets.UTF_8);

使用之后,发现 snakeyaml 读取 yaml 文件后会丢失文件中的注释。 网上搜了一大堆文档后说是 snakeyaml 可以支持处理注释,最后发现也只是处理 java 类的字段上的注释而已。

然后觉得可以自己处理下注释,又在网上翻到有人也做过这样操作。
指路 👉 snakeyaml操作yml文件中注释的处理

但里面的逻辑貌似还存在一些小小问题,于是参考这位老哥的思路,自己优化了下代码:

大致思路:逐行读取文件,遇到存在注释的行则将其与最近的无注释行记录下来。在修改完yaml文件后,再进行替换,将注释替换回来。

具体逻辑参考代码,逻辑比较简单,但考虑的还算比较全面。
(有两点要注意:1.如果 value 用引号包裹并存在# 的话,会被误判,但这个正则貌似有点难写,就没考虑了,我这的yaml没有这个场景; 2. 同样是引号包裹的字符串,重写的时候引号会被去掉。这个暂时也没处理,要处理的话,可能也是用正则替换一下)

工具类代码

maven 依赖:

        <dependency><groupId>org.yamlgroupId><artifactId>snakeyamlartifactId><version>2.0version>dependency><dependency><groupId>commons-iogroupId><artifactId>commons-ioartifactId><version>2.11.0version>dependency><dependency><groupId>org.apache.commonsgroupId><artifactId>commons-lang3artifactId><version>3.9version>dependency>        
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class CommentUtils {public static final String END = "END###";public static final Pattern COMMENT_LINE = Pattern.compile("^\\s*#.*$");public static final Pattern BLANK_LINE = Pattern.compile("^\\s*$");// 带注释的有效行,  使用非贪婪模式匹配有效内容public static final Pattern LINE_WITH_COMMENT = Pattern.compile("^(.*?)\\s+#.*$");@Data@AllArgsConstructorpublic static class Comment {private String lineNoComment;private String lineWithComment;private Integer indexInDuplicates;    // 存在相同行时的索引 (不同key下相同的行, 如 a:\n name: 1  和  b:\n name: 1 )private boolean isEndLine() {return END.equals(lineNoComment);}}@SneakyThrowspublic static CommentHolder buildCommentHolder(File file) {List<Comment> comments = new ArrayList<>();Map<String, Integer> duplicatesLineIndex = new HashMap<>();CommentHolder holder = new CommentHolder(comments);List<String> lines = FileUtils.readLines(file, StandardCharsets.UTF_8);// 末尾加个标志, 防止最后的注释丢失lines.add(END);StringBuilder lastLinesWithComment = new StringBuilder();for (String line : lines) {if (StringUtils.isBlank(line) || BLANK_LINE.matcher(line).find()) {lastLinesWithComment.append(line).append('\n');continue;}// 注释行/空行 都拼接起来if (COMMENT_LINE.matcher(line).find()) {lastLinesWithComment.append(line).append('\n');continue;}String lineNoComment = line;boolean lineWithComment = false;// 如果是带注释的行, 也拼接起来, 但是记录非注释的部分Matcher matcher = LINE_WITH_COMMENT.matcher(line);if (matcher.find()) {lineNoComment = matcher.group(1);lineWithComment = true;}// 去除后面的空格lineNoComment = lineNoComment.replace("\\s*$", "");// 记录下相同行的索引Integer idx = duplicatesLineIndex.merge(lineNoComment, 1, Integer::sum);// 存在注释内容, 记录if (lastLinesWithComment.length() > 0 || lineWithComment) {lastLinesWithComment.append(line);comments.add(new Comment(lineNoComment, lastLinesWithComment.toString(), idx));// 清空注释内容lastLinesWithComment = new StringBuilder();}}return holder;}@AllArgsConstructorpublic static class CommentHolder {private List<Comment> comments;/*** 通过正则表达式移除匹配的行 (防止被移除的行携带注释信息, 导致填充注释时无法正常匹配)*/public void removeLine(String regex) {comments.removeIf(comment -> comment.getLineNoComment().matches(regex));}@SneakyThrowspublic void fillComments(File file) {if (comments == null || comments.isEmpty()) {return;}if (file == null || !file.exists()) {throw new IllegalArgumentException("file is not exist");}List<String> lines = FileUtils.readLines(file, StandardCharsets.UTF_8);Map<String, Integer> duplicatesLineIndex = new HashMap<>();int comIdx = 0;StringBuilder res = new StringBuilder();for (String line : lines) {Integer idx = duplicatesLineIndex.merge(line, 1, Integer::sum);Comment comment = getOrDefault(comments, comIdx, null);if (comment != null &&Objects.equals(line, comment.lineNoComment)&& Objects.equals(comment.indexInDuplicates, idx)) {res.append(comment.lineWithComment).append('\n');comIdx++;} else {res.append(line).append('\n');}}Comment last = comments.get(comments.size() - 1);if (last.isEndLine()) {res.append(last.lineWithComment.substring(0, last.lineWithComment.indexOf(END)));}FileUtils.write(file, res.toString(), StandardCharsets.UTF_8);}}public static <T> T getOrDefault(List<T> vals, int index, T defaultVal) {if (vals == null || vals.isEmpty()) {return defaultVal;}if (index >= vals.size()) {return defaultVal;}T v = vals.get(index);return v == null ? defaultVal : v;}}

使用示例:

    private void modifyYaml() {File f = new File("xxx.yaml");String s = FileUtils.readFileToString(f, StandardCharsets.UTF_8);Yaml yaml = new Yaml();// 记录 yaml 文件的注释信息CommentUtils.CommentHolder holder = CommentUtils.buildCommentHolder(f);LinkedHashMap map = yaml.loadAs(s, LinkedHashMap.class);map.put("key", "value");map.remove("name", "aaaa");s = yaml.dump(map);FileUtils.writeStringToFile(f, s, StandardCharsets.UTF_8);// 因为删掉了 name 行, 这里也同步移除一下, 防止错位holder.removeLine("^\\s*name:\\s.*$");// 填充注释信息holder.fillComments(f);}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部