diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/MiniMessage.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/MiniMessage.java index c814baf61..3b925c538 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/MiniMessage.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/MiniMessage.java @@ -235,11 +235,14 @@ interface Builder extends Buildable.Builder { @NotNull Builder editTags(final @NotNull Consumer adder); /** - * Allows to enable strict mode (disabled by default). + * Enables strict mode (disabled by default). * *

By default, MiniMessage will allow {@link net.kyori.adventure.text.minimessage.tag.Inserting#allowsChildren() child-allowing} tags to be implicitly closed. When strict mode * is enabled, all child-allowing tags which are {@code } must be explicitly {@code } as well.

* + *

Additionally, the {@link net.kyori.adventure.text.minimessage.tag.ParserDirective#RESET reset tag} is disabled in this mode. + * Any usage of this tag will throw a parser exception if strict mode is enabled.

+ * * @param strict if strict mode should be enabled * @return this builder * @since 4.10.0 diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/parser/TokenParser.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/parser/TokenParser.java index 886ccdf1a..95069d52f 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/parser/TokenParser.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/parser/TokenParser.java @@ -40,6 +40,7 @@ import net.kyori.adventure.text.minimessage.parser.node.TagPart; import net.kyori.adventure.text.minimessage.parser.node.TextNode; import net.kyori.adventure.text.minimessage.tag.Inserting; +import net.kyori.adventure.text.minimessage.tag.ParserDirective; import net.kyori.adventure.text.minimessage.tag.Tag; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -53,8 +54,6 @@ @ApiStatus.Internal public final class TokenParser { private static final int MAX_DEPTH = 16; - public static final String RESET = "reset"; - public static final String RESET_2 = "r"; // minimessage tags public static final char TAG_START = '<'; public static final char TAG_END = '>'; @@ -364,33 +363,30 @@ private static ElementNode buildTree( case OPEN_TAG: final TagNode tagNode = new TagNode(node, token, message, tagProvider); - if (isReset(tagNode.name())) { - // tags get special treatment and don't appear in the tree - // instead, they close all currently open tags - - if (strict) { - throw new ParsingExceptionImpl(" tags are not allowed when strict mode is enabled", message, token); - } - node = root; - } else { - if (tagNameChecker.test(tagNode.name())) { - final Tag transformation = tagProvider.resolve(tagNode); - if (transformation == null) { - // something went wrong, ignore it - // if strict mode is enabled this will throw an exception for us - node.addChild(new TextNode(node, token, message)); - } else { - // This is a recognized tag, goes in the tree - tagNode.tag(transformation); - node.addChild(tagNode); - if (!(transformation instanceof Inserting) || ((Inserting) transformation).allowsChildren()) { - node = tagNode; // TODO: self-terminating tags (i.e. ) don't set this, so they don't have children - } + if (tagNameChecker.test(tagNode.name())) { + final Tag tag = tagProvider.resolve(tagNode); + if (tag == null) { + // something went wrong, ignore it + // if strict mode is enabled this will throw an exception for us + node.addChild(new TextNode(node, token, message)); + } else if (tag == ParserDirective.RESET) { + // tags get special treatment and don't appear in the tree + // instead, they close all currently open tags + if (strict) { + throw new ParsingExceptionImpl(" tags are not allowed when strict mode is enabled", message, token); } + node = root; } else { - // not recognized, plain text - node.addChild(new TextNode(node, token, message)); + // 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 + } } + } else { + // not recognized, plain text + node.addChild(new TextNode(node, token, message)); } break; // OPEN_TAG @@ -407,12 +403,15 @@ private static ElementNode buildTree( } final String closeTagName = closeValues.get(0); - if (isReset(closeTagName)) { - // This is a synthetic node, closing it means nothing in the context of building a tree - continue; - } - if (!tagNameChecker.test(closeTagName)) { + if (tagNameChecker.test(closeTagName)) { + final Tag tag = tagProvider.resolve(closeTagName); + + if (tag == ParserDirective.RESET) { + // This is a synthetic node, closing it means nothing in the context of building a tree + continue; + } + } else { // tag does not exist, so treat it as text node.addChild(new TextNode(node, token, message)); continue; @@ -488,10 +487,6 @@ private static ElementNode buildTree( return root; } - private static boolean isReset(final String input) { - return input.equalsIgnoreCase(RESET) || input.equalsIgnoreCase(RESET_2); - } - /** * Determine if a set of close string parts closes the given list of open tag parts. If the open parts starts with * the set of close parts, then this method returns {@code true}. diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/ParserDirective.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/ParserDirective.java new file mode 100644 index 000000000..813cb785f --- /dev/null +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/ParserDirective.java @@ -0,0 +1,50 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.minimessage.tag; + +import org.jetbrains.annotations.ApiStatus; + +/** + * Tags implementing this interface are used to provide directives, or instructions, to the parser directly. + * + * @see #RESET + * @since 4.10.0 + */ +@ApiStatus.NonExtendable +public /* sealed */ interface ParserDirective extends Tag { + /** + * Instructs the parser to reset all style, events, insertions, etc. + * + *

If {@link net.kyori.adventure.text.minimessage.MiniMessage.Builder#strict(boolean) strict mode} is enabled, usage of + * this tag is disallowed and a parse exception will be thrown if this tag is present.

+ * + * @since 4.10.0 + */ + Tag RESET = new ParserDirective() { + @Override + public String toString() { + return "ParserDirective.RESET"; + } + }; +} diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/Tag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/Tag.java index 35f6130ee..cde2dd565 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/Tag.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/Tag.java @@ -40,11 +40,12 @@ /** * A tag definition for the MiniMessage language. * - *

All implementations of {@code Tag} must implement one of {@link Inserting}, {@link Modifying}, or {@link PreProcess}.

+ *

All implementations of {@code Tag} must implement one of {@link Inserting}, {@link Modifying}, + * {@link ParserDirective} or {@link PreProcess}.

* * @since 4.10.0 */ -public /* sealed */ interface Tag /* permits Inserting, Modifying, PreProcess, /internal/ AbstractTag */ { +public /* sealed */ interface Tag /* permits Inserting, Modifying, ParserDirective, PreProcess, /internal/ AbstractTag */ { /** * Create a tag that inserts the content literally into the parse string. diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/StandardTags.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/StandardTags.java index d03e73cfb..ff5ccec77 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/StandardTags.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/StandardTags.java @@ -29,6 +29,7 @@ import java.util.stream.Stream; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.tag.ParserDirective; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; /** @@ -39,6 +40,8 @@ * @since 4.10.0 */ public final class StandardTags { + private static final String RESET_TAG = "reset"; + private StandardTags() { } @@ -62,6 +65,7 @@ private StandardTags() { private static final TagResolver FONT = TagResolver.resolver(FontTag.FONT, FontTag::create); private static final TagResolver GRADIENT = TagResolver.resolver(GradientTag.GRADIENT, GradientTag::create); private static final TagResolver RAINBOW = TagResolver.resolver(RainbowTag.RAINBOW, RainbowTag::create); + private static final TagResolver RESET = TagResolver.resolver(RESET_TAG, ParserDirective.RESET); private static final TagResolver ALL = TagResolver.builder() .resolvers( HOVER_EVENT, @@ -73,7 +77,8 @@ private StandardTags() { FONT, DECORATION, GRADIENT, - RAINBOW + RAINBOW, + RESET ) .build(); @@ -171,6 +176,16 @@ public static TagResolver rainbow() { return RAINBOW; } + /** + * Get a resolver for the {@value RESET_TAG} tag. + * + * @return a resolver for the {@value RESET_TAG} tag. + * @since 4.10.0 + */ + public static TagResolver reset() { + return RESET; + } + /** * Get a resolver that handles all default standard tags. *