Exemple #1
0
        public MojiParserResult Parse(string text)
        {
            var tagStack = new TagStack();
            var result   = new MojiParserResult();

            // Crappy state machine variables
            ParserState state         = ParserState.ReadString;
            string      tagIdentifier = "";
            string      tagParameter  = "";
            string      textPart      = "";
            int         pos           = 0;

            // Only used for warning/error messages.
            int row = 1;
            int col = 0;

            for (; pos < text.Length; pos++)
            {
                char c = text[pos];

                // Increment column (character) counter, only used for warning/error messages.
                // This is reset to 0 every time a `\n` character is found.
                col++;

                // A '<' tells us that a tag type identifier is coming.
                // If the identifier is not a valid identifier the game will ignore it. It will render the closing character '>' normally.
                // TODO: Can you escape a '<' in some way? E.g. like this '\<'?
                if (c == '<')
                {
                    if (textPart.Length > 0)
                    {
                        // Create MojiPart for whatever that is read up untill now.
                        // It's important that a new TagStack is is created.
                        var part = new MojiTextPart(textPart, new TagStack(tagStack));
                        result.Parts.Add(part);
                        textPart = "";
                    }

                    state = ParserState.ReadTagIdentifier;
                    continue;
                }

                if (c == '>')
                {
                    if (state == ParserState.ReadTagIdentifier)
                    {
                        // If closing tag
                        if (tagIdentifier.StartsWith("/"))
                        {
                            string substr = tagIdentifier.Substring(1);
                            tagStack.Pop(substr);
                            tagIdentifier = "";
                            state         = ParserState.ReadString;
                            continue;
                        }
                        else
                        {
                            result.Notices.Add(MojiParserNotice.CreateError($"Tag is closed before a parameter is given. Line: {row}   Char: {col}"));
                            break;
                        }
                    }

                    if (state == ParserState.ReadTagParameter)
                    {
                        // Tag identifier and parameter closed.

                        try
                        {
                            var tag = CreateTag(tagIdentifier, tagParameter, row, col);

                            if (tag is MojiIconTag iconTag)
                            {
                                // Create a MojiIconPart and don't push the tag to the tagStack.
                                var iconPart = new MojiIconPart(iconTag);
                                result.Parts.Add(iconPart);
                            }
                            else
                            {
                                // Push STYL and SIZE tags to the tagStack.
                                tagStack.Push(tag);
                            }
                        }
                        catch (Exception ex)
                        {
                            result.Notices.Add(MojiParserNotice.CreateError(ex.Message));
                            break;
                        }

                        state         = ParserState.ReadString;
                        tagIdentifier = "";
                        tagParameter  = "";
                        //textPart = "";
                        continue;
                    }
                }

                if (c == ' ')
                {
                    if (state == ParserState.ReadTagIdentifier)
                    {
                        state = ParserState.ReadTagParameter;
                        continue;
                    }

                    if (state == ParserState.ReadTagParameter)
                    {
                        result.Notices.Add(MojiParserNotice.CreateError($"Unexpected whitespace inside tag parameter. Line: {row}   Char: {col}"));
                        break;
                    }
                }

                if (c == '\n')
                {
                    // Only used for warning/error messages.
                    col = 0;
                    row++;
                }

                switch (state)
                {
                case ParserState.ReadString:
                    textPart += c;
                    break;

                case ParserState.ReadTagIdentifier:
                    tagIdentifier += c;
                    break;

                case ParserState.ReadTagParameter:
                    tagParameter += c;
                    break;

                default:
                    break;
                }
            }

            // Add text if textPart is not empty.
            // Usually when a new tag is read all the text that is read before it will be added to the Parts lists.
            // However when there are no tags in the string then this will never happen,
            // to still add the read text in those cased we do this check here.
            if (textPart != "")
            {
                var part = new MojiTextPart(textPart, new TagStack(tagStack));
                result.Parts.Add(part);
            }

            if (pos != text.Length || result.HasErrors)
            {
                // Show error when pos != text.Length (aka for-loop has been cancelled early)
                // Also show error when result.HasErrors (because in those cases the for-loop will also be cancelled), though this *should* be covered in the first check.
                result.Notices.Add(MojiParserNotice.CreateError("Couldn't parse the full text because an error was encountered."));
            }
            else if (!tagStack.IsEmpty)
            {
                // Show warning when tagStack != empty.
                // This happens when a tag is opened but not closed.
                // E.g. `<SIZE 17>Hello world.`, notice the lack of the `</SIZE>` tag.
                result.Notices.Add(MojiParserNotice.CreateWarning("Not all tags are closed."));
            }

            foreach (var unclosedTag in tagStack.Tags)
            {
                result.Notices.Add(MojiParserNotice.CreateWarning($"There is a <{unclosedTag.Type}> tag that does not have a matching </{unclosedTag.Type}> tag."));
            }

            return(result);
        }
Exemple #2
0
 public TagStack(TagStack other)
 {
     tags = other.tags.ToList();
 }