Skip to content

Commit

Permalink
text-minimessage: Allow tags to be closed in one tag
Browse files Browse the repository at this point in the history
This simplifies the language for cases where certain tags have zero
children.

Fixes GH-704
Fixes GH-523
  • Loading branch information
zml2008 committed Feb 26, 2022
1 parent b73c0f4 commit 46e2f61
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 37 deletions.
Expand Up @@ -98,6 +98,7 @@ private void processTokens(final @NotNull StringBuilder sb, final @NotNull Strin
break;
case OPEN_TAG:
case CLOSE_TAG:
case OPEN_CLOSE_TAG:
// extract tag name
if (token.childTokens().isEmpty()) {
sb.append(richMessage, token.startIndex(), token.endIndex());
Expand Down
Expand Up @@ -128,7 +128,6 @@ void mark() {
}

void popToMark() {
this.completeTag();
if (this.tagLevel == 0) {
return;
}
Expand All @@ -139,7 +138,6 @@ void popToMark() {
}

void popAll() {
this.completeTag();
while (this.tagLevel > 0) {
final String tag = this.activeTags[--this.tagLevel];
if (tag != MARK) {
Expand Down Expand Up @@ -193,15 +191,6 @@ public TokenEmitter argument(final String arg) {
return this.argument(serialized, QuotingOverride.QUOTED); // always quote tokens
}

@Override
public Collector selfClosing(final String token) {
this.completeTag();
this.consumer.append(TokenParser.TAG_START);
this.escapeTagContent(token, QuotingOverride.UNQUOTED);
this.midTag = true; // TODO: `<tag/>` syntax
return this;
}

@Override
public Collector text(final String text) {
this.completeTag();
Expand Down Expand Up @@ -276,17 +265,21 @@ static void appendEscaping(final StringBuilder builder, final String text, final

@Override
public Collector pop() {
this.completeTag();
this.emitClose(this.popTag(false));
return this;
}

private void emitClose(final @NotNull String tag) {
// currently: we don't keep any arguments, does it ever make sense to?
this.consumer.append(TokenParser.TAG_START)
.append(TokenParser.CLOSE_TAG);
this.escapeTagContent(tag, QuotingOverride.UNQUOTED);
this.consumer.append(TokenParser.TAG_END);
if (this.midTag) {
this.consumer.append(TokenParser.CLOSE_TAG).append(TokenParser.TAG_END);
this.midTag = false;
} else {
this.consumer.append(TokenParser.TAG_START)
.append(TokenParser.CLOSE_TAG);
this.escapeTagContent(tag, QuotingOverride.UNQUOTED);
this.consumer.append(TokenParser.TAG_END);
}
}

// ClaimCollector
Expand Down
Expand Up @@ -213,8 +213,10 @@ public static void parseString(final String message, final MatchedTokenConsumer<

// closing tags start with </
TokenType thisType = TokenType.OPEN_TAG;
if (boundsCheck(message, marker, 1) && message.charAt(marker + 1) == CLOSE_TAG) {
if (boundsCheck(message, marker, 1) && message.charAt(marker + 1) == CLOSE_TAG) { // </content>
thisType = TokenType.CLOSE_TAG;
} else if (boundsCheck(message, marker, 2) && message.charAt(i - 1) == CLOSE_TAG) { // <content/>
thisType = TokenType.OPEN_CLOSE_TAG;
}
consumer.accept(marker, currentTokenEnd, thisType);
state = FirstPassState.NORMAL;
Expand Down Expand Up @@ -254,13 +256,13 @@ public static void parseString(final String message, final MatchedTokenConsumer<
private static void parseSecondPass(final String message, final List<Token> 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;
Expand Down Expand Up @@ -362,6 +364,7 @@ private static ElementNode 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);
Expand All @@ -380,8 +383,8 @@ private static ElementNode 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. <tag/>) 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 {
Expand Down
Expand Up @@ -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;
}
Expand Up @@ -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.
*
* <p>Must be called after {@link #tag(String)} or {@link #selfClosing(String)}, but before any call to {@link #text(String)}.</p>
* <p>Must be called after {@link #tag(String)}, but before any call to {@link #text(String)}.</p>
*
* @param args args to add
* @return this emitter
Expand All @@ -69,7 +60,7 @@ public interface TokenEmitter {
/**
* Add a single argument to the current tag.
*
* <p>Must be called after {@link #tag(String)} or {@link #selfClosing(String)}, but before any call to {@link #text(String)}.</p>
* <p>Must be called after {@link #tag(String)}, but before any call to {@link #text(String)}.</p>
*
* @param arg argument to add
* @return this emitter
Expand All @@ -80,7 +71,7 @@ public interface TokenEmitter {
/**
* Add a single argument to the current tag.
*
* <p>Must be called after {@link #tag(String)} or {@link #selfClosing(String)}, but before any call to {@link #text(String)}.</p>
* <p>Must be called after {@link #tag(String)}, but before any call to {@link #text(String)}.</p>
*
* @param arg argument to add
* @param quotingPreference an argument-specific quoting instruction
Expand All @@ -92,7 +83,7 @@ public interface TokenEmitter {
/**
* Add a single argument to the current tag.
*
* <p>Must be called after {@link #tag(String)} or {@link #selfClosing(String)}, but before any call to {@link #text(String)}.</p>
* <p>Must be called after {@link #tag(String)}, but before any call to {@link #text(String)}.</p>
*
* @param arg argument to add, serialized as a nested MiniMessage string
* @return this emitter
Expand Down
Expand Up @@ -440,4 +440,33 @@ void testTreeOutput() {

assertEquals(expected, tree.toString());
}

@Test
void testTagsSelfClosable() {
final String input = "<red>hello <lang:gameMode.creative/> 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 = "<red/>things";

final Component parsed = Component.text().append(
Component.text("", NamedTextColor.RED),
Component.text("things")
)
.build();

this.assertParsedEquals(parsed, input);
}
}
Expand Up @@ -36,7 +36,7 @@
class KeybindTagTest extends AbstractTest {
@Test
void testSerializeKeyBind() {
final String expected = "Press <key:key.jump></key> to jump!";
final String expected = "Press <key:key.jump/> to jump!";

final TextComponent.Builder builder = Component.text()
.content("Press ")
Expand Down
Expand Up @@ -38,7 +38,7 @@
class TranslatableTagTest extends AbstractTest {
@Test
void testSerializeTranslatable() {
final String expected = "You should get a <lang:block.minecraft.diamond_block></lang>!";
final String expected = "You should get a <lang:block.minecraft.diamond_block/>!";

final TextComponent.Builder builder = Component.text()
.content("You should get a ")
Expand Down

0 comments on commit 46e2f61

Please sign in to comment.