diff --git a/Gameloop.Vdf/VdfConvert.cs b/Gameloop.Vdf/VdfConvert.cs index bb8a5bd..91f89c7 100644 --- a/Gameloop.Vdf/VdfConvert.cs +++ b/Gameloop.Vdf/VdfConvert.cs @@ -8,13 +8,18 @@ namespace Gameloop.Vdf public static class VdfConvert { public static string Serialize(VToken value) + { + return Serialize(value, VdfSerializerSettings.Common); + } + + public static string Serialize(VToken value, VdfSerializerSettings settings) { if (value == null) throw new ArgumentNullException(nameof(value)); StringBuilder stringBuilder = new StringBuilder(256); StringWriter stringWriter = new StringWriter(stringBuilder, CultureInfo.InvariantCulture); - (new VdfSerializer()).Serialize(stringWriter, value); + (new VdfSerializer(settings)).Serialize(stringWriter, value); return stringWriter.ToString(); } diff --git a/Gameloop.Vdf/VdfSerializer.cs b/Gameloop.Vdf/VdfSerializer.cs index 8af95cc..41135a9 100644 --- a/Gameloop.Vdf/VdfSerializer.cs +++ b/Gameloop.Vdf/VdfSerializer.cs @@ -15,7 +15,7 @@ public VdfSerializer(VdfSerializerSettings settings) public void Serialize(TextWriter textWriter, VToken value) { - using (VdfWriter vdfWriter = new VdfTextWriter(textWriter)) + using (VdfWriter vdfWriter = new VdfTextWriter(textWriter, _settings)) value.WriteTo(vdfWriter); } diff --git a/Gameloop.Vdf/VdfStructure.cs b/Gameloop.Vdf/VdfStructure.cs index bc77563..e3bc74b 100644 --- a/Gameloop.Vdf/VdfStructure.cs +++ b/Gameloop.Vdf/VdfStructure.cs @@ -10,5 +10,47 @@ public static class VdfStructure // Conditionals public const string ConditionalXbox360 = "$X360", ConditionalWin32 = "$WIN32"; public const string ConditionalWindows = "$WINDOWS", ConditionalOSX = "$OSX", ConditionalLinux = "$LINUX", ConditionalPosix = "$POSIX"; + + // Escapes + private const uint EscapeMapLength = 128; + private static readonly bool[] EscapeExistsMap; + private static readonly char[] EscapeMap, UnescapeMap; + private static readonly char[,] EscapeConversions = + { + { '\n', 'n' }, + { '\t', 't' }, + { '\v', 'v' }, + { '\b', 'b' }, + { '\r', 'r' }, + { '\f', 'f' }, + { '\a', 'a' }, + { '\\', '\\' }, + { '?' , '?' }, + { '\'', '\'' }, + { '\"', '\"' } + }; + + static VdfStructure() + { + EscapeExistsMap = new bool[EscapeMapLength]; + EscapeMap = new char[EscapeMapLength]; + UnescapeMap = new char[EscapeMapLength]; + + for (int index = 0; index < EscapeMapLength; index++) + EscapeMap[index] = UnescapeMap[index] = (char) index; + + for (int index = 0; index < EscapeConversions.GetLength(0); index++) + { + char unescaped = EscapeConversions[index, 0], escaped = EscapeConversions[index, 1]; + + EscapeExistsMap[unescaped] = true; + EscapeMap[unescaped] = escaped; + UnescapeMap[escaped] = unescaped; + } + } + + public static bool IsEscapable(char ch) => (ch < EscapeMapLength && EscapeExistsMap[ch]); + public static char GetEscape(char ch) => (ch < EscapeMapLength) ? EscapeMap[ch] : ch; + public static char GetUnescape(char ch) => (ch < EscapeMapLength) ? UnescapeMap[ch] : ch; } } diff --git a/Gameloop.Vdf/VdfTextReader.cs b/Gameloop.Vdf/VdfTextReader.cs index afae6a3..63ea580 100644 --- a/Gameloop.Vdf/VdfTextReader.cs +++ b/Gameloop.Vdf/VdfTextReader.cs @@ -6,20 +6,6 @@ namespace Gameloop.Vdf public class VdfTextReader : VdfReader { private const int DefaultBufferSize = 1024; - private static readonly char[][] EscapeConversions = - { - new[] { 'n' , '\n' }, - new[] { 't' , '\t' }, - new[] { 'v' , '\v' }, - new[] { 'b' , '\b' }, - new[] { 'r' , '\r' }, - new[] { 'f' , '\f' }, - new[] { 'a' , '\a' }, - new[] { '\\', '\\' }, - new[] { '?' , '?' }, - new[] { '\'', '\'' }, - new[] { '\"', '\"' }, - }; private readonly TextReader _reader; private readonly char[] _charBuffer, _tokenBuffer; @@ -60,7 +46,7 @@ public override bool ReadToken() if (curChar == VdfStructure.Escape) { - _tokenBuffer[_tokenSize++] = !Settings.UsesEscapeSequences ? curChar : FindConversion(_charBuffer[++_charPos]); + _tokenBuffer[_tokenSize++] = !Settings.UsesEscapeSequences ? curChar : VdfStructure.GetUnescape(_charBuffer[++_charPos]); _charPos++; continue; } @@ -181,20 +167,6 @@ private bool EnsureBuffer() return _charsLen != 0; } - /// - /// Converts the given escape code to an escape character. - /// - /// The escape code. - /// the escape character. - private static char FindConversion(char ch) - { - foreach (char[] conv in EscapeConversions) - if (conv[0] == ch) - return conv[1]; - - return ch; - } - public override void Close() { base.Close(); diff --git a/Gameloop.Vdf/VdfTextWriter.cs b/Gameloop.Vdf/VdfTextWriter.cs index 2c28725..1bba1ea 100644 --- a/Gameloop.Vdf/VdfTextWriter.cs +++ b/Gameloop.Vdf/VdfTextWriter.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; namespace Gameloop.Vdf { @@ -8,7 +9,9 @@ public class VdfTextWriter : VdfWriter private readonly TextWriter _writer; private int _indentationLevel; - public VdfTextWriter(TextWriter writer) + public VdfTextWriter(TextWriter writer) : this(writer, VdfSerializerSettings.Default) { } + + public VdfTextWriter(TextWriter writer, VdfSerializerSettings settings) : base(settings) { if (writer == null) throw new ArgumentNullException(nameof(writer)); @@ -21,7 +24,7 @@ public override void WriteKey(string key) { AutoComplete(State.Key); _writer.Write(VdfStructure.Quote); - _writer.Write(key); + WriteEscapedString(key); _writer.Write(VdfStructure.Quote); } @@ -29,7 +32,7 @@ public override void WriteValue(VValue value) { AutoComplete(State.Value); _writer.Write(VdfStructure.Quote); - _writer.Write(value); + WriteEscapedString(value.ToString()); _writer.Write(VdfStructure.Quote); } @@ -81,6 +84,26 @@ private void AutoComplete(State next) CurrentState = next; } + private void WriteEscapedString(string str) + { + if (!Settings.UsesEscapeSequences) + { + _writer.Write(str); + return; + } + + foreach (char ch in str) + { + if (!VdfStructure.IsEscapable(ch)) + _writer.Write(ch); + else + { + _writer.Write(VdfStructure.Escape); + _writer.Write(VdfStructure.GetEscape(ch)); + } + } + } + public override void Close() { base.Close(); diff --git a/Gameloop.Vdf/VdfWriter.cs b/Gameloop.Vdf/VdfWriter.cs index 363d828..b4d53fe 100644 --- a/Gameloop.Vdf/VdfWriter.cs +++ b/Gameloop.Vdf/VdfWriter.cs @@ -4,11 +4,16 @@ namespace Gameloop.Vdf { public abstract class VdfWriter : IDisposable { + public VdfSerializerSettings Settings { get; } public bool CloseOutput { get; set; } protected internal State CurrentState { get; protected set; } - protected VdfWriter() + protected VdfWriter() : this(VdfSerializerSettings.Default) { } + + protected VdfWriter(VdfSerializerSettings settings) { + Settings = settings; + CurrentState = State.Start; CloseOutput = true; }