tokens) {
for (final Token token : tokens) {
final TokenType type = token.type();
- if (type != TokenType.OPEN_TAG && type != TokenType.CLOSE_TAG) {
+ if (type != TokenType.OPEN_TAG && type != TokenType.OPEN_CLOSE_TAG && type != TokenType.CLOSE_TAG) {
continue;
}
// Only look inside the tag <[/] and >
- final int startIndex = type == TokenType.OPEN_TAG ? token.startIndex() + 1 : token.startIndex() + 2;
- final int endIndex = token.endIndex() - 1;
+ final int startIndex = type == TokenType.CLOSE_TAG ? token.startIndex() + 2 : token.startIndex() + 1;
+ final int endIndex = type == TokenType.OPEN_CLOSE_TAG ? token.endIndex() - 2 : token.endIndex() - 1;
SecondPassState state = SecondPassState.NORMAL;
boolean escaped = false;
@@ -365,6 +367,7 @@ private static RootNode buildTree(
break;
case OPEN_TAG:
+ case OPEN_CLOSE_TAG:
final TagNode tagNode = new TagNode(node, token, message, tagProvider);
if (tagNameChecker.test(tagNode.name())) {
final Tag tag = tagProvider.resolve(tagNode);
@@ -383,8 +386,8 @@ private static RootNode buildTree(
// This is a recognized tag, goes in the tree
tagNode.tag(tag);
node.addChild(tagNode);
- if (!(tag instanceof Inserting) || ((Inserting) tag).allowsChildren()) {
- node = tagNode; // TODO: self-terminating tags (i.e. ) don't set this, so they don't have children
+ if (type != TokenType.OPEN_CLOSE_TAG && (!(tag instanceof Inserting) || ((Inserting) tag).allowsChildren())) {
+ node = tagNode;
}
}
} else {
diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/parser/TokenType.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/parser/TokenType.java
index d2f6c0a68..2ca8538ed 100644
--- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/parser/TokenType.java
+++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/parser/TokenType.java
@@ -31,6 +31,7 @@
public enum TokenType {
TEXT,
OPEN_TAG,
+ OPEN_CLOSE_TAG, // one token that both opens and closes a tag
CLOSE_TAG,
TAG_VALUE;
}
diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/serializer/TokenEmitter.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/serializer/TokenEmitter.java
index 2acdd3222..c0163188f 100644
--- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/serializer/TokenEmitter.java
+++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/serializer/TokenEmitter.java
@@ -41,19 +41,10 @@ public interface TokenEmitter {
*/
@NotNull TokenEmitter tag(final @NotNull String token); // TODO: some sort of TagFlags, with things like SELF_CLOSING, CLOSE_WITH_ARGUMENTS, etc?
- /**
- * Create a self-contained tag without arguments.
- *
- * @param token the token to emit
- * @return this emitter
- * @since 4.10.0
- */
- @NotNull TokenEmitter selfClosing(final @NotNull String token);
-
/**
* Add arguments to the current tag.
*
- * Must be called after {@link #tag(String)} or {@link #selfClosing(String)}, but before any call to {@link #text(String)}.
+ * Must be called after {@link #tag(String)}, but before any call to {@link #text(String)}.
*
* @param args args to add
* @return this emitter
@@ -69,7 +60,7 @@ public interface TokenEmitter {
/**
* Add a single argument to the current tag.
*
- * Must be called after {@link #tag(String)} or {@link #selfClosing(String)}, but before any call to {@link #text(String)}.
+ * Must be called after {@link #tag(String)}, but before any call to {@link #text(String)}.
*
* @param arg argument to add
* @return this emitter
@@ -80,7 +71,7 @@ public interface TokenEmitter {
/**
* Add a single argument to the current tag.
*
- * Must be called after {@link #tag(String)} or {@link #selfClosing(String)}, but before any call to {@link #text(String)}.
+ * Must be called after {@link #tag(String)}, but before any call to {@link #text(String)}.
*
* @param arg argument to add
* @param quotingPreference an argument-specific quoting instruction
@@ -92,7 +83,7 @@ public interface TokenEmitter {
/**
* Add a single argument to the current tag.
*
- * Must be called after {@link #tag(String)} or {@link #selfClosing(String)}, but before any call to {@link #text(String)}.
+ * Must be called after {@link #tag(String)}, but before any call to {@link #text(String)}.
*
* @param arg argument to add, serialized as a nested MiniMessage string
* @return this emitter
diff --git a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/MiniMessageParserTest.java b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/MiniMessageParserTest.java
index 61a66c2f6..f4a219f3e 100644
--- a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/MiniMessageParserTest.java
+++ b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/MiniMessageParserTest.java
@@ -440,4 +440,33 @@ void testTreeOutput() {
assertEquals(expected, tree.toString());
}
+
+ @Test
+ void testTagsSelfClosable() {
+ final String input = "hello there";
+
+ final Component parsed = Component.text()
+ .content("hello ")
+ .color(NamedTextColor.RED)
+ .append(
+ Component.translatable("gameMode.creative"),
+ Component.text(" there")
+ )
+ .build();
+
+ this.assertParsedEquals(parsed, input);
+ }
+
+ @Test
+ void testIgnorableSelfClosable() {
+ final String input = "things";
+
+ final Component parsed = Component.text().append(
+ Component.text("", NamedTextColor.RED),
+ Component.text("things")
+ )
+ .build();
+
+ this.assertParsedEquals(parsed, input);
+ }
}
diff --git a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/standard/KeybindTagTest.java b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/standard/KeybindTagTest.java
index 4df675e7f..8b250168d 100644
--- a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/standard/KeybindTagTest.java
+++ b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/standard/KeybindTagTest.java
@@ -36,7 +36,7 @@
class KeybindTagTest extends AbstractTest {
@Test
void testSerializeKeyBind() {
- final String expected = "Press to jump!";
+ final String expected = "Press to jump!";
final TextComponent.Builder builder = Component.text()
.content("Press ")
diff --git a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/standard/TranslatableTagTest.java b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/standard/TranslatableTagTest.java
index 2aecd6f34..20f463a64 100644
--- a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/standard/TranslatableTagTest.java
+++ b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/standard/TranslatableTagTest.java
@@ -38,7 +38,7 @@
class TranslatableTagTest extends AbstractTest {
@Test
void testSerializeTranslatable() {
- final String expected = "You should get a !";
+ final String expected = "You should get a !";
final TextComponent.Builder builder = Component.text()
.content("You should get a ")