use of net.kyori.adventure.text.minimessage.tag.Tag in project adventure by KyoriPowered.
the class TokenParser method buildTree.
/*
* Build a tree from the OPEN_TAG and CLOSE_TAG tokens
*/
private static RootNode buildTree(@NotNull final TagProvider tagProvider, @NotNull final Predicate<String> tagNameChecker, @NotNull final List<Token> tokens, @NotNull final String message, @NotNull final String originalMessage, final boolean strict) throws ParsingException {
final RootNode root = new RootNode(message, originalMessage);
ElementNode node = root;
for (final Token token : tokens) {
final TokenType type = token.type();
switch(type) {
case TEXT:
node.addChild(new TextNode(node, token, message));
break;
case OPEN_TAG:
case OPEN_CLOSE_TAG:
// Check if this even is a valid tag
final Token tagNamePart = token.childTokens().get(0);
final String tagName = message.substring(tagNamePart.startIndex(), tagNamePart.endIndex());
if (!TagInternals.sanitizeAndCheckValidTagName(tagName)) {
// This wouldn't be a valid tag, just parse it as text instead!
node.addChild(new TextNode(node, token, message));
break;
}
final TagNode tagNode = new TagNode(node, token, message, tagProvider);
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) {
// instead, they close all currently open tags
if (strict) {
throw new ParsingExceptionImpl("<reset> tags are not allowed when strict mode is enabled", message, token);
}
node = root;
} else {
// This is a recognized tag, goes in the tree
tagNode.tag(tag);
node.addChild(tagNode);
if (type != TokenType.OPEN_CLOSE_TAG && (!(tag instanceof Inserting) || ((Inserting) tag).allowsChildren())) {
node = tagNode;
}
}
} else {
// not recognized, plain text
node.addChild(new TextNode(node, token, message));
}
// OPEN_TAG
break;
case CLOSE_TAG:
final List<Token> childTokens = token.childTokens();
if (childTokens.isEmpty()) {
throw new IllegalStateException("CLOSE_TAG token somehow has no children - " + "the parser should not allow this. Original text: " + message);
}
final ArrayList<String> closeValues = new ArrayList<>(childTokens.size());
for (final Token childToken : childTokens) {
closeValues.add(TagPart.unquoteAndEscape(message, childToken.startIndex(), childToken.endIndex()));
}
final String closeTagName = closeValues.get(0);
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;
}
ElementNode parentNode = node;
while (parentNode instanceof TagNode) {
final List<TagPart> openParts = ((TagNode) parentNode).parts();
if (tagCloses(closeValues, openParts)) {
if (parentNode != node && strict) {
final String msg = "Unclosed tag encountered; " + ((TagNode) node).name() + " is not closed, because " + closeValues.get(0) + " was closed first.";
throw new ParsingExceptionImpl(msg, message, parentNode.token(), node.token(), token);
}
final ElementNode par = parentNode.parent();
if (par != null) {
node = par;
} else {
throw new IllegalStateException("Root node matched with close tag value, " + "this should not be possible. Original text: " + message);
}
break;
}
parentNode = parentNode.parent();
}
if (parentNode == null || parentNode instanceof RootNode) {
// This means the closing tag didn't match to anything
// Since open tags which don't match to anything is never an error, neither is this
node.addChild(new TextNode(node, token, message));
break;
}
// CLOSE_TAG
break;
default:
// ignore other tags
break;
}
}
if (strict && root != node) {
final ArrayList<TagNode> openTags = new ArrayList<>();
{
ElementNode n = node;
while (n != null) {
if (n instanceof TagNode) {
openTags.add((TagNode) n);
} else {
break;
}
n = n.parent();
}
}
final Token[] errorTokens = new Token[openTags.size()];
final StringBuilder sb = new StringBuilder("All tags must be explicitly closed while in strict mode. " + "End of string found with open tags: ");
int i = 0;
final ListIterator<TagNode> iter = openTags.listIterator(openTags.size());
while (iter.hasPrevious()) {
final TagNode n = iter.previous();
errorTokens[i++] = n.token();
sb.append(n.name());
if (iter.hasPrevious()) {
sb.append(", ");
}
}
throw new ParsingExceptionImpl(sb.toString(), message, errorTokens);
}
return root;
}
use of net.kyori.adventure.text.minimessage.tag.Tag in project adventure by KyoriPowered.
the class MiniMessageParser method parseToTree.
@NotNull
RootNode parseToTree(@NotNull final String richMessage, @NotNull final ContextImpl context) {
final TagResolver combinedResolver = TagResolver.resolver(this.tagResolver, context.extraTags());
final Consumer<String> debug = context.debugOutput();
if (debug != null) {
debug.accept("Beginning parsing message ");
debug.accept(richMessage);
debug.accept("\n");
}
final TokenParser.TagProvider transformationFactory;
if (debug != null) {
transformationFactory = (name, args, token) -> {
try {
debug.accept("Attempting to match node '");
debug.accept(name);
debug.accept("'");
if (token != null) {
debug.accept(" at column ");
debug.accept(String.valueOf(token.startIndex()));
}
debug.accept("\n");
@Nullable final Tag transformation = combinedResolver.resolve(name, new ArgumentQueueImpl<>(context, args), context);
if (transformation == null) {
debug.accept("Could not match node '");
debug.accept(name);
debug.accept("'\n");
} else {
debug.accept("Successfully matched node '");
debug.accept(name);
debug.accept("' to tag ");
debug.accept(transformation instanceof Examinable ? ((Examinable) transformation).examinableName() : transformation.getClass().getName());
debug.accept("\n");
}
return transformation;
} catch (final ParsingException e) {
if (token != null && e instanceof ParsingExceptionImpl) {
final ParsingExceptionImpl impl = (ParsingExceptionImpl) e;
if (impl.tokens().length == 0) {
impl.tokens(new Token[] { token });
}
}
debug.accept("Could not match node '");
debug.accept(name);
debug.accept("' - ");
debug.accept(e.getMessage());
debug.accept("\n");
return null;
}
};
} else {
transformationFactory = (name, args, token) -> {
try {
return combinedResolver.resolve(name, new ArgumentQueueImpl<>(context, args), context);
} catch (final ParsingException ignored) {
return null;
}
};
}
final Predicate<String> tagNameChecker = name -> {
final String sanitized = TokenParser.TagProvider.sanitizePlaceholderName(name);
return combinedResolver.has(sanitized);
};
final String preProcessed = TokenParser.resolvePreProcessTags(richMessage, transformationFactory);
context.message(preProcessed);
// Then, once MiniMessage placeholders have been inserted, we can do the real parse
final RootNode root = TokenParser.parse(transformationFactory, tagNameChecker, preProcessed, richMessage, context.strict());
if (debug != null) {
debug.accept("Text parsed into element tree:\n");
debug.accept(root.toString());
}
return root;
}
Aggregations