private static RootNode buildTree(
    final @NotNull TagProvider tagProvider,
    final @NotNull Predicate<String> tagNameChecker,
    final @NotNull List<Token> tokens,
    final @NotNull String message,
    final @NotNull 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) {
              // <reset> tags get special treatment and don't appear in the tree
              // 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));
          }
          break; // OPEN_TAG

        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;
          }
          break; // CLOSE_TAG
        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;
  }
Parses a string, providing information on matched tokens to the matched token consumer.

@param message the message
@param lenient whether to allow section symbols
@param consumer the consumer
@since 4.10.0
text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/internal/parser/TokenParser.java