internal string ParseInner(string input) { if (string.IsNullOrEmpty(input)) { return(input); } var output = ""; var escaped = false; GrammarToken token = null; for (var i = 0; i < input.Length; i++) { var ch = input[i]; if (escaped) { AddCharToToken(ch, ref output, token, true); escaped = false; continue; } if (ch == '\\') { AddCharToToken(ch, ref output, token, true); escaped = true; continue; } switch (ch) { case '[': // Start new action token. var newToken = new ActionToken(this, i + 1, token); if (token == null) { token = newToken; } else { token.AddChild(newToken); } newToken.AddChar(ch); break; case ']': // Close highest action token. If any inner tokens are unfinished, they resolve to their raw text. var action = token?.FindLowestOpenOfType(TagType.Action); if (action == null) { // No open action. Add ] to text as normal. AddCharToToken(ch, ref output, token); break; } else { action.AddChar(ch); } action.Resolve(); break; case '#': // If lowest open node is a tag, close it. Otherwise, open a new tag. if (token == null) { token = new TagToken(this, i + 1, null); token.AddChar(ch); break; } var lowest = token.FindLowestOpenToken(); if (lowest.Type == TagType.Tag) { lowest.AddChar(ch); lowest.Resolve(); break; } var newTag = new TagToken(this, i + 1, lowest); lowest.AddChild(newTag); newTag.AddChar(ch); break; default: AddCharToToken(ch, ref output, token); break; } if (token != null && token.IsResolved) { output += token.Resolved; token = null; } } return(output); }