[java] SnakeYAML을 사용한 YAML 파일의 주석 추가 및 제거

YAML 파일은 데이터를 구조화하고 전달하기 위한 경량 마크업 언어입니다. SnakeYAML은 Java에서 YAML 파일을 처리하기 위한 라이브러리로 많이 사용됩니다. 이번 포스트에서는 SnakeYAML을 사용하여 YAML 파일에 주석을 추가하고 제거하는 방법을 알아보겠습니다.

SnakeYAML 설치

SnakeYAML을 사용하기 위해서는 먼저 해당 라이브러리를 프로젝트에 추가해야 합니다. Maven을 사용하는 경우, pom.xml 파일에 다음 의존성을 추가합니다:

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.27</version>
</dependency>

Gradle을 사용하는 경우, build.gradle 파일에 다음 의존성을 추가합니다:

dependencies {
    implementation 'org.yaml:snakeyaml:1.27'
}

주석 추가

SnakeYAML을 사용하여 YAML 파일에 주석을 추가하려면 다음 순서를 따르면 됩니다:

  1. YAML 파일을 로드하여 파싱합니다.
  2. 주석을 추가할 위치를 식별합니다.
  3. Yaml 객체의 dump 메소드를 사용하여 파싱된 YAML 데이터를 다시 YAML 형식의 문자열로 변환합니다.
  4. 변환된 문자열에 주석을 추가합니다.
  5. 최종적으로 주석이 추가된 YAML 형식의 문자열을 파일에 저장합니다.

다음은 SnakeYAML을 사용하여 YAML 파일에 주석을 추가하는 예제 코드입니다:

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.nodes.*;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.introspector.PropertyUtils;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class YAMLCommentExample {

    public static void main(String[] args) throws IOException {
        // YAML 파일 로드
        Yaml yaml = new Yaml(new SafeConstructor());
        InputStream inputStream = new FileInputStream("example.yml");
        Map<String, Object> data = yaml.load(inputStream);

        // 주석 추가 위치 식별
        Node node = yaml.compose(new FileReader("example.yml"));
        Map<ValueNode, CommentLine> commentMap = new HashMap<>();

        for (NodeTuple tuple : ((MappingNode)node).getValue()) {
            ValueNode keyNode = (ValueNode) tuple.getKeyNode();
            CommentLine commentLine = new CommentLine("주석입니다");

            commentMap.put(keyNode, commentLine);
        }

        // 파싱된 YAML 데이터를 YAML 형식의 문자열로 변환
        Representer representer = new Representer();
        representer.setPropertyUtils(new PropertyUtils() {
            @Override
            public Property getProperty(Class<? extends Object> type, String name) {
                if (name.startsWith("!")) {
                    name = name.substring(1);
                }
                return super.getProperty(type, name);
            }
        });
        StringWriter writer = new StringWriter();
        yaml.dump(data, writer);

        // 주석 추가
        String yamlString = writer.toString();

        for (Map.Entry<ValueNode, CommentLine> entry : commentMap.entrySet()) {
            ValueNode keyNode = entry.getKey();
            CommentLine commentLine = entry.getValue();
            yamlString = insertComment(yamlString, keyNode, commentLine);
        }

        // 최종 YAML 파일 저장
        FileWriter fileWriter = new FileWriter("example_with_comment.yml");
        fileWriter.write(yamlString);
        fileWriter.close();
    }

    // 주석 삽입 메소드
    public static String insertComment(String yamlString, Node node, CommentLine commentLine) {
        String comment = "#" + commentLine.getValue();

        String[] lines = yamlString.split("\n");

        int lineNum = node.getEndMark().getLine();
        String indent = getIndent(lines[lineNum - 1]);

        return lines[node.getStartMark().getLine() - 1] + "\n" +
                indent + comment + "\n" +
                lines[node.getStartMark().getLine() - 1].replaceFirst(indent, "");
    }

    // 들여쓰기 문자열 반환 메소드
    public static String getIndent(String line) {
        String indent = "";
        for (char c : line.toCharArray()) {
            if (!Character.isWhitespace(c)) {
                break;
            }
            indent += c;
        }
        return indent;
    }

    // 주석 클래스
    public static class CommentLine {
        private String value;

        public CommentLine(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }
}

위의 예제 코드는 example.yml 파일을 로드하고, example_with_comment.yml 파일에 주석을 추가한 뒤 저장합니다. example.yml은 다음의 내용을 가진 YAML 파일이라고 가정합니다:

key1: value1
key2: value2

주석이 추가된 example_with_comment.yml 파일은 다음과 같은 형태를 가집니다:

key1: value1
# 주석입니다
key2: value2

주석 제거

SnakeYAML을 사용하여 YAML 파일에서 주석을 제거하려면 다음 순서를 따르면 됩니다:

  1. YAML 파일을 로드하여 파싱합니다.
  2. 주석을 제거할 위치를 식별합니다.
  3. Yaml 객체의 dump 메소드를 사용하여 파싱된 YAML 데이터를 다시 YAML 형식의 문자열로 변환합니다.
  4. 변환된 문자열에서 주석을 제거합니다.
  5. 최종적으로 주석이 제거된 YAML 형식의 문자열을 파일에 저장합니다.

다음은 SnakeYAML을 사용하여 YAML 파일에서 주석을 제거하는 예제 코드입니다:

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.nodes.*;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.introspector.PropertyUtils;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class YAMLCommentRemovalExample {

    public static void main(String[] args) throws IOException {
        // YAML 파일 로드
        Yaml yaml = new Yaml(new SafeConstructor());
        InputStream inputStream = new FileInputStream("example_with_comment.yml");
        Map<String, Object> data = yaml.load(inputStream);

        // 주석 제거 위치 식별
        Node node = yaml.compose(new FileReader("example_with_comment.yml"));
        Map<Node, String> commentMap = new HashMap<>();

        for (NodeTuple tuple : ((MappingNode)node).getValue()) {
            Node keyNode = tuple.getKeyNode();
            String comment = getCommentedLine(tuple);

            commentMap.put(keyNode, comment);
        }

        // 파싱된 YAML 데이터를 YAML 형식의 문자열로 변환
        Representer representer = new Representer();
        representer.setPropertyUtils(new PropertyUtils() {
            @Override
            public Property getProperty(Class<? extends Object> type, String name) {
                if (name.startsWith("!")) {
                    name = name.substring(1);
                }
                return super.getProperty(type, name);
            }
        });
        StringWriter writer = new StringWriter();
        yaml.dump(data, writer);

        // 주석 제거
        String yamlString = writer.toString();

        for (Map.Entry<Node, String> entry : commentMap.entrySet()) {
            Node keyNode = entry.getKey();
            String comment = entry.getValue();
            yamlString = removeComment(yamlString, keyNode, comment);
        }

        // 최종 YAML 파일 저장
        FileWriter fileWriter = new FileWriter("example_without_comment.yml");
        fileWriter.write(yamlString);
        fileWriter.close();
    }

    // 주석된 줄 반환 메소드
    private static String getCommentedLine(NodeTuple tuple) {
        String line = tuple.getValueNode().getStartMark().getLine() + ": " +
                tuple.getValueNode().toString();

        while (line.endsWith("\n")) {
            line = line.substring(0, line.length() - 1);
        }

        while (line.endsWith(" ")) {
            line = line.substring(0, line.length() - 1);
        }

        return line;
    }

    // 주석 제거 메소드
    private static String removeComment(String yamlString, Node node, String comment) {
        String[] lines = yamlString.split("\n");
        int lineNumber = node.getEndMark().getLine();
        String indent = getIndent(lines[lineNumber - 1]);

        String lineToReplace = lines[node.getStartMark().getLine() - 1];
        String removedCommentLine = lineToReplace.replaceFirst(indent + "#" + comment, "");
        lines[node.getStartMark().getLine() - 1] = removedCommentLine;

        return String.join("\n", lines);
    }

    // 들여쓰기 문자열 반환 메소드
    private static String getIndent(String line) {
        String indent = "";
        for (char c : line.toCharArray()) {
            if (!Character.isWhitespace(c)) {
                break;
            }
            indent += c;
        }
        return indent;
    }
}

위의 예제 코드는 example_with_comment.yml 파일을 로드하고, 주석을 제거한 후 example_without_comment.yml 파일에 저장합니다. example_with_comment.yml은 주석이 추가된 example.yml 파일이라고 가정합니다.

주석이 제거된 example_without_comment.yml 파일은 다음과 같은 형태를 가집니다:

key1: value1
key2: value2

이로써 SnakeYAML을 사용하여 YAML 파일에 주석을 추가하고 제거하는 방법을 알아보았습니다. SnakeYAML을 통해 YAML 파일을 더욱 유연하게 다룰 수 있습니다. SnakeYAML의 다른 기능과 사용 방법에 대해서는 SnakeYAML 공식 문서를 참고하시기 바랍니다.