mirror of
https://github.com/Tribufu/ServerManagers
synced 2026-08-09 12:41:06 +00:00
source code checkin
This commit is contained in:
parent
5f8fb2c825
commit
7e57b72e35
675 changed files with 168433 additions and 0 deletions
464
src/NeXtVdf/VdfDeserializer.cs
Normal file
464
src/NeXtVdf/VdfDeserializer.cs
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace NeXt.Vdf
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for al Deserialization exceptions
|
||||
/// </summary>
|
||||
public class VdfDeserializationException : Exception
|
||||
{
|
||||
public VdfDeserializationException() : base() { }
|
||||
public VdfDeserializationException(string message) : base() { }
|
||||
public VdfDeserializationException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A character was unexpected during deserialization
|
||||
/// </summary>
|
||||
public class UnexpectedCharacterException : VdfDeserializationException
|
||||
{
|
||||
public UnexpectedCharacterException(string message, char c) : base(message)
|
||||
{
|
||||
Character = c;
|
||||
}
|
||||
|
||||
public char Character {get; private set;}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes Vdf formatted text into a VdfValue
|
||||
/// </summary>
|
||||
public class VdfDeserializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a VdfDeserializer object
|
||||
/// </summary>
|
||||
/// <param name="vdfText">the text to deserialize</param>
|
||||
public VdfDeserializer(string vdfText)
|
||||
{
|
||||
VdfText = vdfText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a vdfdeserializer object
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the file to deserialize</param>
|
||||
/// <returns></returns>
|
||||
public static VdfDeserializer FromFile(string filePath)
|
||||
{
|
||||
using (var reader = new StreamReader(filePath))
|
||||
{
|
||||
return new VdfDeserializer(reader.ReadToEnd());
|
||||
}
|
||||
}
|
||||
|
||||
private string VdfText;
|
||||
|
||||
private enum TokenType
|
||||
{
|
||||
String,
|
||||
TableStart,
|
||||
TableEnd,
|
||||
Comment,
|
||||
None,
|
||||
}
|
||||
|
||||
private struct Token
|
||||
{
|
||||
public TokenType type;
|
||||
public string content;
|
||||
}
|
||||
|
||||
private enum CharacterType
|
||||
{
|
||||
Whitespace,
|
||||
Newline,
|
||||
SequenceDelimiter,
|
||||
CommentDelimiter,
|
||||
TableOpen,
|
||||
TableClose,
|
||||
EscapeChar,
|
||||
Char,
|
||||
}
|
||||
private CharacterType GetCharType(char c)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case '\n': return CharacterType.Newline;
|
||||
case '\r':
|
||||
case '\t':
|
||||
case ' ':
|
||||
{
|
||||
return CharacterType.Whitespace;
|
||||
}
|
||||
|
||||
case '{': return CharacterType.TableOpen;
|
||||
case '}': return CharacterType.TableClose;
|
||||
case '\\': return CharacterType.EscapeChar;
|
||||
case '/': return CharacterType.CommentDelimiter;
|
||||
case '"': return CharacterType.SequenceDelimiter;
|
||||
default: return CharacterType.Char;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// gets the character a single escape char represents
|
||||
/// </summary>
|
||||
/// <param name="c"></param>
|
||||
/// <returns></returns>
|
||||
private char GetUnescapedChar(char c)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case 'n': return '\n';
|
||||
case 't': return '\t';
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the next full text (or as much as possible if incomplete)
|
||||
/// </summary>
|
||||
/// <param name="s">text to search in</param>
|
||||
/// <param name="startindex">first index to look at</param>
|
||||
/// <param name="endIndex">the index the returned string ended at</param>
|
||||
/// <param name="fullStop">true if the string was over, false if it was incomplete</param>
|
||||
/// <returns></returns>
|
||||
private string GetTextToDelimiter(string s, int startindex, out int endIndex, out bool fullStop)
|
||||
{
|
||||
fullStop = true;
|
||||
bool openEscape = false;
|
||||
bool EscapedSequence = GetCharType(s[startindex]) == CharacterType.SequenceDelimiter;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(int i = startindex; i < s.Length; i++)
|
||||
{
|
||||
switch(GetCharType(s[i]))
|
||||
{
|
||||
case CharacterType.SequenceDelimiter:
|
||||
{
|
||||
if(!openEscape && EscapedSequence && i > startindex)
|
||||
{
|
||||
endIndex = i + 1;
|
||||
return sb.ToString();
|
||||
}
|
||||
else if(!EscapedSequence)
|
||||
{
|
||||
throw new UnexpectedCharacterException("Non-Escape sequences cannot contain sequence delimiters", s[i]);
|
||||
}
|
||||
else if(openEscape)
|
||||
{
|
||||
sb.Append(GetUnescapedChar(s[i]));
|
||||
openEscape = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CharacterType.Whitespace:
|
||||
{
|
||||
if(EscapedSequence)
|
||||
{
|
||||
sb.Append(s[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
endIndex = i;
|
||||
return sb.ToString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharacterType.EscapeChar:
|
||||
{
|
||||
if(openEscape)
|
||||
{
|
||||
sb.Append(GetUnescapedChar(s[i]));
|
||||
}
|
||||
openEscape = !openEscape;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if(openEscape)
|
||||
{
|
||||
sb.Append(GetUnescapedChar(s[i]));
|
||||
openEscape = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(s[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
endIndex = s.Length;
|
||||
if(EscapedSequence)
|
||||
{
|
||||
if((GetCharType(s[s.Length-1]) != CharacterType.SequenceDelimiter))
|
||||
{
|
||||
fullStop = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int c = 0;
|
||||
for (int i = s.Length - 2; i >= 0 && GetCharType(s[i]) == CharacterType.EscapeChar; i--)
|
||||
{
|
||||
c++;
|
||||
}
|
||||
|
||||
fullStop = (c % 2) == 0;
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private Token startedToken;
|
||||
private bool unclosedLine = false;
|
||||
private void HandleUnclosedLine(Action<Token> callback, string line)
|
||||
{
|
||||
int endindex;
|
||||
bool isEnd;
|
||||
string text = GetTextToDelimiter("\""+line, 0, out endindex, out isEnd);
|
||||
if (!isEnd)
|
||||
{
|
||||
startedToken.content += text;
|
||||
unclosedLine = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
unclosedLine = false;
|
||||
callback(startedToken);
|
||||
if (endindex < line.Length)
|
||||
{
|
||||
HandleLine(callback, line.Substring(endindex).Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLine(Action<Token> callback, string line)
|
||||
{
|
||||
if(string.IsNullOrEmpty(line))
|
||||
{
|
||||
return;
|
||||
}
|
||||
CharacterType ct = GetCharType(line[0]);
|
||||
switch(ct)
|
||||
{
|
||||
case CharacterType.TableOpen:
|
||||
{
|
||||
callback(new Token(){type=TokenType.TableStart, content=line[0].ToString() });
|
||||
break;
|
||||
}
|
||||
case CharacterType.TableClose:
|
||||
{
|
||||
callback(new Token(){type=TokenType.TableEnd, content=line[0].ToString() });
|
||||
break;
|
||||
}
|
||||
case CharacterType.CommentDelimiter:
|
||||
{
|
||||
if(line.Length < 2 || GetCharType(line[1]) != CharacterType.CommentDelimiter)
|
||||
{
|
||||
throw new UnexpectedCharacterException("Single comment delimiter is not allowed", line[0]);
|
||||
}
|
||||
callback(new Token(){type=TokenType.Comment, content=line });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
int endindex;
|
||||
bool isEnd;
|
||||
string text = GetTextToDelimiter(line, 0, out endindex, out isEnd);
|
||||
if(!isEnd)
|
||||
{
|
||||
startedToken = new Token() { type = TokenType.String, content = text };
|
||||
unclosedLine = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(new Token() { type = TokenType.String, content = text });
|
||||
if (endindex < line.Length)
|
||||
{
|
||||
HandleLine(callback, line.Substring(endindex).Trim());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tokenizes the VdfFormatted string into a list of tokens
|
||||
/// </summary>
|
||||
/// <param name="s">the string to tokenize</param>
|
||||
/// <returns>the token list</returns>
|
||||
private List<Token> Tokenize(string s)
|
||||
{
|
||||
var result = new List<Token>();
|
||||
|
||||
var lines = s.Split('\n').Select((v) => v.Trim());
|
||||
|
||||
foreach(var line in lines)
|
||||
{
|
||||
if(unclosedLine)
|
||||
{
|
||||
HandleUnclosedLine(result.Add, line);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleLine(result.Add, line);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the Vdf string into a VdfValue
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public VdfValue Deserialize()
|
||||
{
|
||||
if(VdfText== null)
|
||||
{
|
||||
throw new ArgumentNullException("s");
|
||||
}
|
||||
if(VdfText.Length < 1)
|
||||
{
|
||||
throw new ArgumentException("s cannot be empty ", "s");
|
||||
}
|
||||
|
||||
var tokens = Tokenize(VdfText);
|
||||
|
||||
if(tokens.Count < 1)
|
||||
{
|
||||
throw new ArgumentException("no tokens found in string", "s");
|
||||
}
|
||||
|
||||
VdfValue root = null;
|
||||
VdfTable current = null;
|
||||
var comments = new List<string>();
|
||||
|
||||
string name = null;
|
||||
|
||||
foreach(var token in tokens)
|
||||
{
|
||||
if(token.type == TokenType.Comment)
|
||||
{
|
||||
comments.Add(token.content.Substring(2));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(root == null)
|
||||
{
|
||||
if (token.type == TokenType.String)
|
||||
{
|
||||
if(name != null)
|
||||
{
|
||||
return new VdfString(name, token.content);
|
||||
}
|
||||
|
||||
name = token.content;
|
||||
}
|
||||
else if (token.type == TokenType.TableStart)
|
||||
{
|
||||
root = new VdfTable(name);
|
||||
if(comments.Count > 0)
|
||||
{
|
||||
foreach(var comment in comments)
|
||||
{
|
||||
root.Comments.Add(comment);
|
||||
}
|
||||
comments.Clear();
|
||||
}
|
||||
current = root as VdfTable;
|
||||
name = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new VdfDeserializationException("Invalid format: First token was not a string");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(name != null)
|
||||
{
|
||||
VdfValue v;
|
||||
if(token.type == TokenType.String)
|
||||
{
|
||||
long i;
|
||||
decimal d;
|
||||
|
||||
if(long.TryParse(token.content, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
|
||||
{
|
||||
v = new VdfLong(name, i);
|
||||
}
|
||||
else if(decimal.TryParse(token.content, NumberStyles.Number, CultureInfo.InvariantCulture, out d))
|
||||
{
|
||||
v = new VdfDecimal(name, d);
|
||||
}
|
||||
else
|
||||
{
|
||||
v = new VdfString(name, token.content);
|
||||
}
|
||||
if (comments.Count > 0)
|
||||
{
|
||||
foreach (var comment in comments)
|
||||
{
|
||||
v.Comments.Add(comment);
|
||||
}
|
||||
comments.Clear();
|
||||
}
|
||||
name = null;
|
||||
current.Add(v);
|
||||
}
|
||||
else if (token.type == TokenType.TableStart)
|
||||
{
|
||||
v = new VdfTable(name);
|
||||
if (comments.Count > 0)
|
||||
{
|
||||
foreach (var comment in comments)
|
||||
{
|
||||
v.Comments.Add(comment);
|
||||
}
|
||||
comments.Clear();
|
||||
}
|
||||
current.Add(v);
|
||||
name = null;
|
||||
current = v as VdfTable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(token.type == TokenType.String)
|
||||
{
|
||||
name = token.content;
|
||||
}
|
||||
else if(token.type == TokenType.TableEnd)
|
||||
{
|
||||
current = current.Parent as VdfTable;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new VdfDeserializationException("Invalid Format: a name was needed but not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(current != null)
|
||||
{
|
||||
throw new VdfDeserializationException("Invalid format: unclosed table");
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue