Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Port from snakeyaml-engine] Issue 54: Insert a trailing space when alias is a simple key #309

Merged
merged 12 commits into from
Dec 31, 2024
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,65 @@
package it.krzeminski.snakeyaml.engine.kmp.issues.issue54

import io.kotest.assertions.fail
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
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({
krzema12 marked this conversation as resolved.
Show resolved Hide resolved
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") {
try {
parse(
"""|--- &1
|hash:
| :one: true
| :two: true
| *1: true""".trimMargin()
)
fail("Shouldn't reach here!")
} catch (e: Exception) {
e.message shouldContain "could not find expected ':'"
}
krzema12 marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading