diff --git a/Sledge.Formats.Tests/Valve/TestSerialisedObject.cs b/Sledge.Formats.Tests/Valve/TestSerialisedObject.cs index 8ccaddd..88d2fe5 100644 --- a/Sledge.Formats.Tests/Valve/TestSerialisedObject.cs +++ b/Sledge.Formats.Tests/Valve/TestSerialisedObject.cs @@ -211,4 +211,44 @@ public void TestNestedObjectsWithQuotedNames() Assert.AreEqual("Three", output[0].Children[0].Children[0].Name); Assert.AreEqual("Value", output[0].Children[0].Children[0].Get("Key")); } + + [TestMethod] + public void TestSpecialCharactersInKeyValues() + { + var input = """ + One + { + $key1 "$value1" + $key2 $value2 + !key3 &value3 + !@#$%^&*()_+ value4 + A B + "C" D + E "F" + "G" "H" + $I { } + "$J" { } + } + """; + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(input)); + var fmt = new SerialisedObjectFormatter(); + var output = fmt.Deserialize(stream).ToList(); + + Assert.AreEqual(1, output.Count); + Assert.AreEqual("One", output[0].Name); + Assert.AreEqual("$value1", output[0].Get("$key1")); + Assert.AreEqual("$value2", output[0].Get("$key2")); + Assert.AreEqual("&value3", output[0].Get("!key3")); + Assert.AreEqual("value4", output[0].Get("!@#$%^&*()_+")); + Assert.AreEqual("B", output[0].Get("A")); + Assert.AreEqual("D", output[0].Get("C")); + Assert.AreEqual("F", output[0].Get("E")); + Assert.AreEqual("H", output[0].Get("G")); + Assert.AreEqual(2, output[0].Children.Count); + Assert.AreEqual("$I", output[0].Children[0].Name); + Assert.AreEqual(0, output[0].Children[0].Children.Count); + Assert.AreEqual("$J", output[0].Children[1].Name); + Assert.AreEqual(0, output[0].Children[1].Children.Count); + } } \ No newline at end of file diff --git a/Sledge.Formats/Sledge.Formats.csproj b/Sledge.Formats/Sledge.Formats.csproj index 047d2d4..1d368a9 100644 --- a/Sledge.Formats/Sledge.Formats.csproj +++ b/Sledge.Formats/Sledge.Formats.csproj @@ -13,10 +13,10 @@ https://github.com/LogicAndTrick/sledge-formats Git half-life quake valve liblist vdf - Additions to the IFileSystem interface + Fix parsing issues when serialised object keys/values are unquoted and contain special characters MIT - 1.3.0 + 1.3.1 diff --git a/Sledge.Formats/Valve/SerialisedObjectFormatter.cs b/Sledge.Formats/Valve/SerialisedObjectFormatter.cs index eb35a9e..8b5e54e 100644 --- a/Sledge.Formats/Valve/SerialisedObjectFormatter.cs +++ b/Sledge.Formats/Valve/SerialisedObjectFormatter.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using Sledge.Formats.Tokens; +using Sledge.Formats.Tokens.Readers; namespace Sledge.Formats.Valve { @@ -112,7 +113,23 @@ public static void Print(SerialisedObject obj, TextWriter tw, int tabs = 0) Tokens.Symbols.CloseBrace }; - private static readonly Tokeniser Tokeniser = new Tokeniser(Symbols); + private static readonly Tokeniser Tokeniser; + + static SerialisedObjectFormatter() + { + Tokeniser = new Tokeniser( + new SingleLineCommentTokenReader(), + new StringTokenReader(), + new UnsignedIntegerTokenReader(), + new SymbolTokenReader(Symbols), + new NameTokenReader(IsValidNameCharacter, IsValidNameCharacter) + ); + } + + private static bool IsValidNameCharacter(char c) + { + return c != '"' && c != '{' && c != '}' && !char.IsWhiteSpace(c) && !char.IsControl(c); + } /// /// Parse a structure from a stream @@ -182,7 +199,7 @@ public static IEnumerable Parse(TextReader reader) break; } - else if (t.Type == TokenType.String && it.Current.Type == TokenType.String) + else if (it.Current.Type == TokenType.String || it.Current.Type == TokenType.Name) { if (current == null) throw new TokenParsingException(t, "No structure to add key/values to"); @@ -192,10 +209,6 @@ public static IEnumerable Parse(TextReader reader) break; } - else if (t.Type == TokenType.Name) - { - throw new TokenParsingException(t, "Expected structure open brace"); - } else { throw new TokenParsingException(t, "Expected string value or open brace to follow string key");