Modifying YAML in java while preserving comments
Asked Answered
C

4

11

How can we modify an existing YAML and preserve comments in it. Is there any Java parser which does that ? For example if i have following YAML:

#This is a test YAML
 name: abcd
 age: 23
#Test YAML ends here.

Is there a way I can edit this Yaml using a java parser and preserve the comments.

Carvey answered 13/4, 2018 at 7:57 Comment(1)
Have you tried the snake yam? Isn't it supported by default?Thereunder
M
5

At least since snakeyaml 2.0 (which I'm using) it is possible to preserve comments. The only drawback seems to be that you cannot parse the YAML to a Map or some other class, because that erases the comment information. That is only stored in snakeyaml's internal Nodes.

LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setProcessComments(true);
DumperOptions dumperOptions = new DumperOptions();
dumperOptions.setProcessComments(true);
Yaml yaml = new Yaml(new Constructor(loaderOptions), new Representer(dumperOptions), dumperOptions, loaderOptions);
Node root;
try (FileReader reader = new FileReader(yamlFile)) {
    root = yaml.compose(reader);
}
try (FileWriter writer = new FileWriter(cloneFile)) {
    yaml.serialize(root, writer);
}

The only difference I get between in- and output are different line endings (can be configured in the DumperOptions) and some of my previously empty lines will have indenting spaces.

Mychal answered 25/8, 2023 at 7:37 Comment(0)
Y
3

As of the time of writing, there is no round-tripping YAML parser for Java. There is the well-known SnakeYAML, which does not preserve comments (see the author's comment here), and a newer project named camel, which I know little of; but it definitely is not round-tripping.

What you can theoretically do is to use SnakeYaml's Yaml.parse and then iterate over the events. Each event has a start and an end mark, giving the start and end line & column of the event. This makes it possible to map the events back into the source and discover the portions of the source that were not parsed into events (presumably comments). Having this mapping, you can now modify the event list and write it back. Finally, you read the result in a second time and discover the gaps between your events where there were comments in the original YAML, but not in the modified YAML, and re-insert those comments, giving you the final YAML with your modifications and the comments.

Of course, this is very complex. I would not advice to do it unless you a) have either a solid understanding of how YAML is structured or are willing to learn it, and b) your use-case justifies this amount of work.

Younglove answered 13/4, 2018 at 8:22 Comment(1)
This is outdated. SnakeYAML 2.0 supports keeping the comments. See the other answer.Harter
S
1

I wrote a groovy script to solve this. The Java version is very similar:

def key = "name"
def value = "efgh"
def yamlFile = new File("file.yaml")
def yamlFileLines = new StringBuilder()
def foundKey = false
yamlFile.text.eachLine { line ->
    if (!foundKey && line.contains("$key:")) {
        line = line.replaceAll(/$key:.*/, "$key: $value")
        foundKey = true
    }
    yamlFileLines.append("$line\n")
}
if (foundKey) {
    yamlFile.text = yamlFileLines.toString()
} else {
    throw new StopExecutionException("Could not find key '$key' in file ${yamlFile.getAbsolutePath()}")
}
Stoichiometric answered 8/5, 2019 at 15:38 Comment(0)
G
0

if you use snakeyaml , you should modify the ScannerImpl file.

notice: read the in-line comment as text

private Token scanPlain() {
        StringBuilder chunks = new StringBuilder();
        Mark startMark = reader.getMark();
        Mark endMark = startMark;
        int indent = this.indent + 1;
        String spaces = "";
        while (true) {
            int c;
            int length = 0;
            // A comment indicates the end of the scalar.
            // read the in-line comment as text
//            if (reader.peek() == '#' && ) {
//                break;
//            }
            while (true) {
                c = reader.peek(length);
                if (Constant.NULL_BL_T_LINEBR.has(c)
                        || (c == ':' && Constant.NULL_BL_T_LINEBR.has(reader.peek(length + 1), flowLevel != 0 ? ",[]{}":""))
                        || (this.flowLevel != 0 && ",?[]{}".indexOf(c) != -1)) {
                    break;
                }
                length++;
            }
            if (length == 0) {
                break;
            }
            this.allowSimpleKey = false;
            chunks.append(spaces);
            chunks.append(reader.prefixForward(length));
            endMark = reader.getMark();
            spaces = scanPlainSpaces();
            // System.out.printf("spaces[%s]\n", spaces);
            if (spaces.length() == 0
                    // read the in-line comment as text
//                    || reader.peek() == '#'
                    || (this.flowLevel == 0 && this.reader.getColumn() < indent)) {
                break;
            }
        }
        return new ScalarToken(chunks.toString(), startMark, endMark, true);
    }
Gallicize answered 3/11, 2020 at 3:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.