diff --git a/Data/Serialization/FileSerializer.cs b/Data/Serialization/FileSerializer.cs index ecf94c72..c6472d26 100644 --- a/Data/Serialization/FileSerializer.cs +++ b/Data/Serialization/FileSerializer.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Text; using TweetDuck.Core.Utils; namespace TweetDuck.Data.Serialization{ @@ -11,6 +12,44 @@ sealed class FileSerializer<T>{ private const string NewLineReal = "\r\n"; private const string NewLineCustom = "\r~\n"; + private static string EscapeLine(string input) => input.Replace("\\", "\\\\").Replace(Environment.NewLine, "\\\r\n"); + private static string UnescapeLine(string input) => input.Replace(NewLineCustom, Environment.NewLine); + + private static string UnescapeStream(StreamReader reader){ + string data = reader.ReadToEnd(); + + StringBuilder build = new StringBuilder(data.Length); + int index = 0; + + while(true){ + int nextIndex = data.IndexOf('\\', index); + + if (nextIndex == -1 || nextIndex+1 >= data.Length){ + break; + } + else{ + build.Append(data.Substring(index, nextIndex-index)); + + char next = data[nextIndex+1]; + + if (next == '\\'){ // convert double backslash to single backslash + build.Append('\\'); + index = nextIndex+2; + } + else if (next == '\r' && nextIndex+2 < data.Length && data[nextIndex+2] == '\n'){ // convert backslash followed by CRLF to custom new line + build.Append(NewLineCustom); + index = nextIndex+3; + } + else{ // single backslash + build.Append('\\'); + index = nextIndex+1; + } + } + } + + return build.Append(data.Substring(index)).ToString(); + } + private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter(); public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data); @@ -36,13 +75,13 @@ public void Write(string file, T obj){ Type type = prop.Value.PropertyType; object value = prop.Value.GetValue(obj); - if (!converters.TryGetValue(type, out ITypeConverter serializer)) { + if (!converters.TryGetValue(type, out ITypeConverter serializer)){ serializer = BasicSerializerObj; } if (serializer.TryWriteType(type, value, out string converted)){ if (converted != null){ - writer.Write($"{prop.Key} {converted.Replace(Environment.NewLine, NewLineCustom)}"); + writer.Write($"{prop.Key} {EscapeLine(converted)}"); writer.Write(NewLineReal); } } @@ -65,7 +104,7 @@ public void Read(string file, T obj){ throw new FormatException("Input appears to be a binary file."); } - foreach(string line in reader.ReadToEnd().Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){ + foreach(string line in UnescapeStream(reader).Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){ int space = line.IndexOf(' '); if (space == -1){ @@ -73,7 +112,7 @@ public void Read(string file, T obj){ } string property = line.Substring(0, space); - string value = line.Substring(space+1).Replace(NewLineCustom, Environment.NewLine); + string value = UnescapeLine(line.Substring(space+1)); if (props.TryGetValue(property, out PropertyInfo info)){ if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)) { diff --git a/tests/Data/TestFileSerializer.cs b/tests/Data/TestFileSerializer.cs index d6aa1e24..34e7283c 100644 --- a/tests/Data/TestFileSerializer.cs +++ b/tests/Data/TestFileSerializer.cs @@ -13,7 +13,9 @@ private enum TestEnum{ private class SerializationTestBasic{ public bool TestBool { get; set; } public int TestInt { get; set; } - public string TestString { get; set; } + public string TestStringBasic { get; set; } + public string TestStringNewLine { get; set; } + public string TestStringBackslash { get; set; } public string TestStringNull { get; set; } public TestEnum TestEnum { get; set; } } @@ -25,7 +27,9 @@ public void TestBasicWriteRead(){ SerializationTestBasic write = new SerializationTestBasic{ TestBool = true, TestInt = -100, - TestString = "abc"+Environment.NewLine+"def", + TestStringBasic = "hello123", + TestStringNewLine = "abc"+Environment.NewLine+"def"+Environment.NewLine, + TestStringBackslash = @"C:\Test\\\Abc\", TestStringNull = null, TestEnum = TestEnum.D }; @@ -38,7 +42,9 @@ public void TestBasicWriteRead(){ Assert.IsTrue(read.TestBool); Assert.AreEqual(-100, read.TestInt); - Assert.AreEqual("abc"+Environment.NewLine+"def", read.TestString); + Assert.AreEqual("hello123", read.TestStringBasic); + Assert.AreEqual("abc"+Environment.NewLine+"def"+Environment.NewLine, read.TestStringNewLine); + Assert.AreEqual(@"C:\Test\\\Abc\", read.TestStringBackslash); Assert.IsNull(read.TestStringNull); Assert.AreEqual(TestEnum.D, read.TestEnum); }