Skip to content

Commit

Permalink
[Port from snakeyaml-engine] Issue 54: Insert a trailing space when a…
Browse files Browse the repository at this point in the history
  • Loading branch information
krzema12 authored Dec 31, 2024
1 parent 6bdb3c5 commit 509b511
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,11 @@ class Emitter(
simpleKeyContext = simpleKey
when (event?.eventId) {
Event.ID.Alias -> {
expectAlias()
expectAlias(simpleKey)
}

Event.ID.Scalar, Event.ID.SequenceStart, Event.ID.MappingStart -> {
processAnchor("&")
processAnchor()
processTag()
handleNodeEvent(event!!.eventId)
}
Expand Down Expand Up @@ -396,9 +396,12 @@ class Emitter(
}
}

private fun expectAlias() {
/**
* @param simpleKey true when this is the alias for a simple key
*/
private fun expectAlias(simpleKey: Boolean) {
state = if (event is AliasEvent) {
processAnchor("*")
processAlias(simpleKey)
states.removeLast()
} else {
throw EmitterException("Expecting Alias.")
Expand Down Expand Up @@ -782,7 +785,7 @@ class Emitter(

//region Anchor, Tag, and Scalar processors.

private fun processAnchor(indicator: String) {
private fun processAnchorOrAlias(indicator: String, trailingWhitespace: Boolean) {
val ev = event as NodeEvent
val anchor: Anchor? = ev.anchor
if (anchor != null) {
Expand All @@ -792,6 +795,19 @@ class Emitter(
writeIndicator(indicator = indicator + anchor, needWhitespace = true)
}
preparedAnchor = null
if (trailingWhitespace) {
writeWhitespace(1)
}
}

private fun processAnchor() {
// no need for trailing space
processAnchorOrAlias("&", false)
}

private fun processAlias(simpleKey: Boolean) {
// because of ':' it needs to add trailing space for simple keys
processAnchorOrAlias("*", simpleKey)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1279,11 +1279,10 @@ class ScannerImpl(

/**
* The YAML 1.2 specification does not restrict characters for anchors and
* aliases. This may lead to problems, see
* [issue 485](https://bitbucket.org/snakeyaml/snakeyaml/issues/485/alias-names-are-too-permissive-compared-to)
* aliases. This may lead to problems.
*
* This implementation tries to follow
* [RFC-0003](https://github.com/yaml/yaml-spec/blob/master/rfc/RFC-0003.md)
* See [alias naming](https://github.com/yaml/libyaml/issues/205#issuecomment-693634465)
* See also [issue 485](https://bitbucket.org/snakeyaml/snakeyaml/issues/485/alias-names-are-too-permissive-compared-to)
*/
private fun scanAnchor(isAnchor: Boolean): Token {
val startMark = reader.getMark()
Expand All @@ -1292,7 +1291,10 @@ class ScannerImpl(
reader.forward()
var length = 0
var c = reader.peek(length)
// Anchor may not contain ",[]{}"
// The future implementation may try to follow RFC-0003:
// https://github.com/yaml/yaml-spec/blob/master/rfc/RFC-0003.md
// and exclude also ':' (colon)
// Anchor may not contain ,[]{}/.*&
while (CharConstants.NULL_BL_T_LINEBR.hasNo(c, ",[]{}/.*&")) {
length++
c = reader.peek(length)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package it.krzeminski.snakeyaml.engine.kmp.issues.issue54

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import it.krzeminski.snakeyaml.engine.kmp.api.Dump
import it.krzeminski.snakeyaml.engine.kmp.api.DumpSettings
import it.krzeminski.snakeyaml.engine.kmp.api.Load
import it.krzeminski.snakeyaml.engine.kmp.api.LoadSettings

/**
* Issue 54: add a space after anchor (when it is a simple key)
*/
class DumpWithoutSpaceTest : FunSpec({
fun parse(data: String): Any? {
val loadSettings = LoadSettings.builder().setAllowRecursiveKeys(true).build();
val load = Load(loadSettings);
return load.loadOne(data);
}

test("The document does not have a space after the *1 alias") {
shouldThrow<Exception> {
parse(
"""|--- &1
|hash:
| :one: true
| :two: true
| *1: true""".trimMargin()
)
}.also {
it.message shouldContain "could not find expected ':'"
}
}

test("The output does include a space after the *1 alias") {
val obj = parse(
"""|--- &1
|hash:
| :one: true
| :two: true
| *1 : true""".trimMargin()
)
obj.shouldNotBeNull()
}

test("Dump and load an alias") {
val map = mutableMapOf<Any, Boolean>(
":one" to true,
)
map[map] = true
val dumpSettings = DumpSettings.builder().build()
val dump = Dump(dumpSettings)
val output = dump.dumpToString(map)
output shouldBe """|&id001
|:one: true
|*id001 : true
|""".trimMargin()
val recursive = parse(output)
recursive.shouldNotBeNull()
}
})
17 changes: 5 additions & 12 deletions src/jvmTest/java/org/snakeyaml/engine/v2/emitter/EmitterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public void testAnchorInMaps() {
}

@Test
@DisplayName("Expected space to separate anchor from colon")
@DisplayName("Expected space to separate alias from colon")
public void testAliasAsKey() {
DumpSettingsBuilder builder = DumpSettings.builder().setDefaultFlowStyle(FlowStyle.FLOW);
// this is VERY BAD code
Expand All @@ -266,20 +266,13 @@ public void testAliasAsKey() {
f.put(f, "a");

String output = dump(builder.build(), f);
// TODO FIXME this YAML is invalid, the colon will be part of Anchor and not the separator
// key:value in the flow.
assertEquals("&id001 {*id001: a}\n", output);
Load load = new Load();
try {
load.loadOne(output);
fail("TODO fix anchor");
} catch (ComposerException e) {
assertTrue(e.getMessage().contains("found undefined alias id001:"), e.getMessage());
}
assertEquals("&id001 {*id001 : a}\n", output);
Load load = new Load(LoadSettings.builder().setAllowRecursiveKeys(true).build());
Object obj = load.loadOne(output);
assertNotNull(obj);
}

public static class MyDumperWriter extends StringWriter implements StreamDataWriter {

}

@Test
Expand Down

0 comments on commit 509b511

Please sign in to comment.