# HG changeset patch # User cin # Date 2017-09-12 16:07:42 # Node ID 5f7a3e1d32b998a703539963ed4ed369b925bc6b # Parent 6fa235c5a76041d84b5159669fd68d8717afc5dc JsonXmlReader performance tuning JsonScanner now operates strings and doesn't parses number and literals. Added SerializationHelpers to common serialize/deserialize operations diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -21,3 +21,5 @@ Implab.Test/Implab.Format.Test/obj/ Implab.Format.Test/bin/ Implab.Format.Test/obj/ packages/ +Implab.Playground/obj/ +Implab.Playground/bin/ diff --git a/Implab.Format.Test/JsonTests.cs b/Implab.Format.Test/JsonTests.cs --- a/Implab.Format.Test/JsonTests.cs +++ b/Implab.Format.Test/JsonTests.cs @@ -5,6 +5,7 @@ using Implab.Xml; using System.Xml; using Implab.Formats; using Implab.Formats.Json; +using System.IO; namespace Implab.Format.Test { [TestFixture] @@ -15,19 +16,19 @@ namespace Implab.Format.Test { using (var scanner = JsonStringScanner.Create(@"9123, -123, 0, 0.1, -0.2, -0.1e3, 1.3E-3, ""some \t\n\u0020 text"", literal []{}:")) { Tuple[] expexted = { - new Tuple(JsonTokenType.Number, 9123d), + new Tuple(JsonTokenType.Number, "9123"), new Tuple(JsonTokenType.ValueSeparator, null), - new Tuple(JsonTokenType.Number, -123d), + new Tuple(JsonTokenType.Number, "-123"), new Tuple(JsonTokenType.ValueSeparator, null), - new Tuple(JsonTokenType.Number, 0d), + new Tuple(JsonTokenType.Number, "0"), new Tuple(JsonTokenType.ValueSeparator, null), - new Tuple(JsonTokenType.Number, 0.1d), + new Tuple(JsonTokenType.Number, "0.1"), new Tuple(JsonTokenType.ValueSeparator, null), - new Tuple(JsonTokenType.Number, -0.2d), + new Tuple(JsonTokenType.Number, "-0.2"), new Tuple(JsonTokenType.ValueSeparator, null), - new Tuple(JsonTokenType.Number, -0.1e3d), + new Tuple(JsonTokenType.Number, "-0.1e3"), new Tuple(JsonTokenType.ValueSeparator, null), - new Tuple(JsonTokenType.Number, 1.3E-3d), + new Tuple(JsonTokenType.Number, "1.3E-3"), new Tuple(JsonTokenType.ValueSeparator, null), new Tuple(JsonTokenType.String, "some \t\n text"), new Tuple(JsonTokenType.ValueSeparator, null), @@ -39,7 +40,7 @@ namespace Implab.Format.Test { new Tuple(JsonTokenType.NameSeparator, null) }; - object value; + string value; JsonTokenType tokenType; for (var i = 0; i < expexted.Length; i++) { @@ -73,7 +74,7 @@ namespace Implab.Format.Test { foreach (var json in bad) { using (var scanner = JsonStringScanner.Create(json)) { try { - object value; + string value; JsonTokenType token; scanner.ReadToken(out value, out token); if (!Object.Equals(value, json)) { @@ -109,11 +110,11 @@ namespace Implab.Format.Test { //DumpJsonParse("{}"); //DumpJsonParse("[]"); DumpJsonParse("{\"one\":1, \"two\":2}"); - DumpJsonParse("[1,2,3]"); + DumpJsonParse("[1,\"\",2,3]"); DumpJsonParse("[{\"info\": [7,8,9]}]"); - DumpJsonFlatParse("[1,2,[3,4],{\"info\": [5,6]},{\"num\": [7,8,null]}, null,[null]]"); + DumpJsonFlatParse("[1,2,\"\",[3,4],{\"info\": [5,6]},{\"num\": [7,8,null]}, null,[null]]"); } - + void AssertRead(XmlReader reader, XmlNodeType expected) { Assert.IsTrue(reader.Read()); Console.WriteLine($"{new string(' ', reader.Depth*2)}{reader}"); @@ -123,7 +124,7 @@ namespace Implab.Format.Test { void DumpJsonParse(string json) { Console.WriteLine($"JSON: {json}"); Console.WriteLine("XML"); - using (var xmlReader = new JsonXmlReader(new JsonParser(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "json" })) { + using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "json" })) { while (xmlReader.Read()) Console.WriteLine($"{new string(' ', xmlReader.Depth * 2)}{xmlReader}"); } @@ -137,7 +138,7 @@ namespace Implab.Format.Test { CloseOutput = false, ConformanceLevel = ConformanceLevel.Document })) - using (var xmlReader = new JsonXmlReader(new JsonParser(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "", FlattenArrays = true })) { + using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "", FlattenArrays = true })) { xmlWriter.WriteNode(xmlReader, false); } } diff --git a/Implab.Playground/App.config b/Implab.Playground/App.config new file mode 100644 --- /dev/null +++ b/Implab.Playground/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Implab.Playground/Implab.Playground.csproj b/Implab.Playground/Implab.Playground.csproj new file mode 100644 --- /dev/null +++ b/Implab.Playground/Implab.Playground.csproj @@ -0,0 +1,68 @@ + + + + + Debug + AnyCPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46} + Exe + Properties + Implab.Playground + Implab.Playground + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + true + + + + + + + + + + + + + + + + + + + + + {f550f1f8-8746-4ad0-9614-855f4c4b7f05} + Implab + + + + + \ No newline at end of file diff --git a/Implab.Playground/Program.cs b/Implab.Playground/Program.cs new file mode 100644 --- /dev/null +++ b/Implab.Playground/Program.cs @@ -0,0 +1,42 @@ +using Implab.Formats.Json; +using Implab.Xml; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace Implab.Playground { + public class Program { + + [XmlRoot(Namespace = "XmlSimpleData")] + public class XmlSimpleModel { + [XmlElement] + public string Name { get; set; } + + [XmlElement] + public int Order { get; set; } + + [XmlElement] + public string[] Items { get; set; } + + } + + static void Main(string[] args) { + var model = new XmlSimpleModel { + Name = "Tablet", + Order = 10, + Items = new string[] { "z1", "z2", "z3" } + }; + + var doc = SerializationHelpers.SerializeAsXmlDocument(model); + + var m2 = SerializationHelpers.DeserializeFromXmlNode(doc.DocumentElement); + + Console.WriteLine("done"); + } + } +} diff --git a/Implab.Playground/Properties/AssemblyInfo.cs b/Implab.Playground/Properties/AssemblyInfo.cs new file mode 100644 --- /dev/null +++ b/Implab.Playground/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Implab.Playground")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Implab.Playground")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("100dfeb0-75be-436f-addf-1f46ef433f46")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Implab.Playground2.psess b/Implab.Playground2.psess new file mode 100644 --- /dev/null +++ b/Implab.Playground2.psess @@ -0,0 +1,75 @@ + + + + Implab.sln + Sampling + None + true + true + Timestamp + Cycles + 50000 + 10 + 10 + + false + + + + false + 500 + + \Память\Обмен страниц/с + \Процессор(_Total)\% загруженности процессора + \Физический диск(_Total)\Средняя длина очереди диска + + + + true + false + false + + false + + + false + + + + Implab.Playground\obj\Debug\Implab.Playground.exe + 01/01/0001 00:00:00 + true + true + false + false + false + false + false + true + false + Executable + Implab.Playground\bin\Release\Implab.Playground.exe + Implab.Playground\bin\Release\ + + + IIS + InternetExplorer + true + false + + false + + + false + + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}|Implab.Playground\Implab.Playground.csproj + Implab.Playground\Implab.Playground.csproj + Implab.Playground + + + + + :PB:{100DFEB0-75BE-436F-ADDF-1F46EF433F46}|Implab.Playground\Implab.Playground.csproj + + + \ No newline at end of file diff --git a/Implab.sln b/Implab.sln --- a/Implab.sln +++ b/Implab.sln @@ -16,11 +16,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab.Fx", "Implab.Fx\Implab.Fx.csproj", "{06E706F8-6881-43EB-927E-FFC503AF6ABC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab.Fx.Test", "Implab.Fx.Test\Implab.Fx.Test.csproj", "{2F31E405-E267-4195-A05D-574093C21209}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab.Format.Test", "Implab.Format.Test\Implab.Format.Test.csproj", "{4D364996-7ECD-4193-8F90-F223FFEA49DA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab.Playground", "Implab.Playground\Implab.Playground.csproj", "{100DFEB0-75BE-436F-ADDF-1F46EF433F46}" +EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug 4.5|Any CPU = Debug 4.5|Any CPU Debug|Any CPU = Debug|Any CPU @@ -52,14 +55,6 @@ Global {06E706F8-6881-43EB-927E-FFC503AF6ABC}.Release 4.5|Any CPU.Build.0 = Release 4.5|Any CPU {06E706F8-6881-43EB-927E-FFC503AF6ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU {06E706F8-6881-43EB-927E-FFC503AF6ABC}.Release|Any CPU.Build.0 = Release|Any CPU - {2F31E405-E267-4195-A05D-574093C21209}.Debug 4.5|Any CPU.ActiveCfg = Debug 4.5|Any CPU - {2F31E405-E267-4195-A05D-574093C21209}.Debug 4.5|Any CPU.Build.0 = Debug 4.5|Any CPU - {2F31E405-E267-4195-A05D-574093C21209}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F31E405-E267-4195-A05D-574093C21209}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F31E405-E267-4195-A05D-574093C21209}.Release 4.5|Any CPU.ActiveCfg = Release 4.5|Any CPU - {2F31E405-E267-4195-A05D-574093C21209}.Release 4.5|Any CPU.Build.0 = Release 4.5|Any CPU - {2F31E405-E267-4195-A05D-574093C21209}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F31E405-E267-4195-A05D-574093C21209}.Release|Any CPU.Build.0 = Release|Any CPU {4D364996-7ECD-4193-8F90-F223FFEA49DA}.Debug 4.5|Any CPU.ActiveCfg = Debug|Any CPU {4D364996-7ECD-4193-8F90-F223FFEA49DA}.Debug 4.5|Any CPU.Build.0 = Debug|Any CPU {4D364996-7ECD-4193-8F90-F223FFEA49DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -68,6 +63,14 @@ Global {4D364996-7ECD-4193-8F90-F223FFEA49DA}.Release 4.5|Any CPU.Build.0 = Release|Any CPU {4D364996-7ECD-4193-8F90-F223FFEA49DA}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D364996-7ECD-4193-8F90-F223FFEA49DA}.Release|Any CPU.Build.0 = Release|Any CPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}.Debug 4.5|Any CPU.ActiveCfg = Debug|Any CPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}.Debug 4.5|Any CPU.Build.0 = Debug|Any CPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}.Release 4.5|Any CPU.ActiveCfg = Release|Any CPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}.Release 4.5|Any CPU.Build.0 = Release|Any CPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {100DFEB0-75BE-436F-ADDF-1F46EF433F46}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Implab/Automaton/DFATable.cs b/Implab/Automaton/DFATable.cs --- a/Implab/Automaton/DFATable.cs +++ b/Implab/Automaton/DFATable.cs @@ -119,7 +119,7 @@ namespace Implab.Automaton { table[i, j] = AutomatonConst.UNREACHABLE_STATE; foreach (var t in this) - table[t.s1,t.edge] = t.s2; + table[t.s1,t.edge] = (byte)t.s2; return table; } diff --git a/Implab/Formats/InputScanner.cs b/Implab/Formats/InputScanner.cs --- a/Implab/Formats/InputScanner.cs +++ b/Implab/Formats/InputScanner.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -30,24 +31,28 @@ namespace Implab.Formats { } public TTag Tag { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return m_tags[m_state]; } } public int Position { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return m_position; } } public bool IsFinal { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return m_final[m_state]; } } - public void Reset() { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResetState() { m_state = m_initialState; } @@ -58,13 +63,8 @@ namespace Implab.Formats { return clone; } - public bool Scan(char[] data, int offset, int length) { - if (length <= 0) { - m_position = offset; - return false; // EOF - } - - var max = offset + length; + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Scan(char[] data, int offset, int max) { var next = m_state; while(offset < max) { diff --git a/Implab/Formats/JSON/JsonParser.cs b/Implab/Formats/JSON/JsonReader.cs rename from Implab/Formats/JSON/JsonParser.cs rename to Implab/Formats/JSON/JsonReader.cs --- a/Implab/Formats/JSON/JsonParser.cs +++ b/Implab/Formats/JSON/JsonReader.cs @@ -6,6 +6,8 @@ using Implab.Automaton.RegularExpression using System.Linq; using Implab.Components; using System.Collections.Generic; +using System.Text; +using System.Globalization; namespace Implab.Formats.Json { /// @@ -24,7 +26,7 @@ namespace Implab.Formats.Json { /// } // Level = 0 /// /// - public class JsonParser : Disposable { + public class JsonReader : Disposable { enum MemberContext { MemberName, @@ -61,7 +63,7 @@ namespace Implab.Formats.Json { static readonly ParserContext _objectContext; static readonly ParserContext _arrayContext; - static JsonParser() { + static JsonReader() { var valueExpression = MakeToken(JsonTokenType.BeginArray, JsonTokenType.BeginObject, JsonTokenType.Literal, JsonTokenType.Number, JsonTokenType.String); var memberExpression = MakeToken(JsonTokenType.String).Cat(MakeToken(JsonTokenType.NameSeparator)).Cat(valueExpression); @@ -124,20 +126,10 @@ namespace Implab.Formats.Json { /// Создает новый парсер на основе строки, содержащей JSON /// /// - public JsonParser(string text) { - Safe.ArgumentNotEmpty(text, "text"); - m_scanner = JsonStringScanner.Create(text); + JsonReader(JsonScanner scanner) { + m_scanner = scanner; } - - /// - /// Создает новый экземпляр парсера, на основе текстового потока. - /// - /// Текстовый поток. - public JsonParser(TextReader reader) { - Safe.ArgumentNotNull(reader, "reader"); - m_scanner = JsonTextScanner.Create(reader); - } - + public int Level { get { return m_stack.Count; } } @@ -169,7 +161,7 @@ namespace Implab.Formats.Json { /// /// true - операция чтения прошла успешно, false - конец данных public bool Read() { - object tokenValue; + string tokenValue; JsonTokenType tokenType; m_memberName = String.Empty; @@ -213,7 +205,7 @@ namespace Implab.Formats.Json { return true; case JsonTokenType.String: if (m_memberContext == MemberContext.MemberName) { - m_memberName = (string)tokenValue; + m_memberName = tokenValue; break; } m_elementType = JsonElementType.Value; @@ -221,11 +213,11 @@ namespace Implab.Formats.Json { return true; case JsonTokenType.Number: m_elementType = JsonElementType.Value; - m_elementValue = tokenValue; + m_elementValue = double.Parse(tokenValue, CultureInfo.InvariantCulture); return true; case JsonTokenType.Literal: m_elementType = JsonElementType.Value; - m_elementValue = ParseLiteral((string)tokenValue); + m_elementValue = ParseLiteral(tokenValue); return true; case JsonTokenType.NameSeparator: m_memberContext = MemberContext.MemberValue; @@ -241,7 +233,7 @@ namespace Implab.Formats.Json { if (m_context.ElementContext != JsonElementContext.None) throw new ParserException("Unexpedted end of data"); - EOF = true; + Eof = true; return false; } @@ -268,7 +260,7 @@ namespace Implab.Formats.Json { /// /// Признак конца потока /// - public bool EOF { + public bool Eof { get; private set; } @@ -289,6 +281,38 @@ namespace Implab.Formats.Json { while (Level != level) Read(); } + + public static JsonReader Create(string file, Encoding encoding) { + return new JsonReader(JsonTextScanner.Create(file, encoding)); + } + + public static JsonReader Create(string file) { + return new JsonReader(JsonTextScanner.Create(file)); + } + + public static JsonReader Create(Stream stream, Encoding encoding) { + return new JsonReader(JsonTextScanner.Create(stream, encoding)); + } + + public static JsonReader Create(Stream stream) { + return new JsonReader(JsonTextScanner.Create(stream)); + } + + public static JsonReader Create(TextReader reader) { + return new JsonReader(JsonTextScanner.Create(reader)); + } + + public static JsonReader ParseString(string data) { + return new JsonReader(JsonStringScanner.Create(data)); + } + + public static JsonReader ParseString(string data, int offset, int length) { + return new JsonReader(JsonStringScanner.Create(data, offset, length)); + } + + public static JsonReader ParseString(char[] data, int offset, int lenght) { + return new JsonReader(JsonStringScanner.Create(data, offset, lenght)); + } } } diff --git a/Implab/Formats/JSON/JsonScanner.cs b/Implab/Formats/JSON/JsonScanner.cs --- a/Implab/Formats/JSON/JsonScanner.cs +++ b/Implab/Formats/JSON/JsonScanner.cs @@ -25,38 +25,94 @@ namespace Implab.Formats.Json { m_length = length; } - bool Read(InputScanner scanner, out JsonGrammar.TokenType tokenType) { - scanner.Reset(); + bool ReadChunk(InputScanner scanner, out JsonGrammar.TokenType tokenType) { + scanner.ResetState(); + + while(scanner.Scan(m_buffer, m_pos, m_length)) { + // scanner requests new data - if (m_pos == m_length) { - m_pos = 0; + if (m_pos != m_length) // capture results for the future + m_tokenBuilder.Append(m_buffer, m_pos, m_length - m_pos); + + // read next data m_length = Read(m_buffer, 0, m_buffer.Length); + if (m_length == 0) { - tokenType = JsonGrammar.TokenType.None; - return false; // EOF + // no data is read + if (scanner.Position == m_pos) { + // scanned hasn't moved, that's the end + m_pos = 0; + tokenType = JsonGrammar.TokenType.None; + return false; + } + + if (scanner.IsFinal) { + m_pos = 0; + tokenType = scanner.Tag; + return true; + } else { + throw new ParserException("Unexpected EOF"); + } } - } - - while(scanner.Scan(m_buffer, m_pos, m_length - m_pos)) { - m_tokenBuilder.Append(m_buffer, m_pos, m_length - m_pos); + m_pos = 0; - m_length = Read(m_buffer, 0, m_buffer.Length); } var scannerPos = scanner.Position; + + // scanner stops as scannerPos + if (!scanner.IsFinal) + throw new ParserException($"Unexpected character '{m_buffer[scannerPos + 1]}'"); + + tokenType = scanner.Tag; + if (scannerPos != m_pos && tokenType == JsonGrammar.TokenType.Number || tokenType == JsonGrammar.TokenType.Literal) + m_tokenBuilder.Append(m_buffer, m_pos, scannerPos - m_pos); + + m_pos = scannerPos; + return true; + } + + bool ReadStringChunk(InputScanner scanner, out JsonGrammar.TokenType tokenType) { + scanner.ResetState(); + + while (scanner.Scan(m_buffer, m_pos, m_length)) { + // scanner requests new data + + if (m_pos != m_length) // capture results for the future + m_tokenBuilder.Append(m_buffer, m_pos, m_length - m_pos); + + // read next data + m_length = Read(m_buffer, 0, m_buffer.Length); + + if (m_length == 0) { + // no data is read + if (scanner.Position == m_pos) { + // scanned hasn't moved, that's the end + m_pos = 0; + tokenType = JsonGrammar.TokenType.None; + return false; + } + + if (scanner.IsFinal) { + m_pos = 0; + tokenType = scanner.Tag; + return true; + } else { + throw new ParserException("Unexpected EOF"); + } + } + + m_pos = 0; + } + var scannerPos = scanner.Position; + + // scanner stops as scannerPos + if (!scanner.IsFinal) + throw new ParserException($"Unexpected character '{m_buffer[scannerPos + 1]}'"); + if (scannerPos != m_pos) { m_tokenBuilder.Append(m_buffer, m_pos, scannerPos - m_pos); m_pos = scannerPos; } - - if (!scanner.IsFinal) { - if (m_length == 0) { - // unexpected EOF - throw new ParserException("Unexpected EOF"); - } else { - // unecpected character - throw new ParserException($"Unexpected character '{m_buffer[m_pos + 1]}'"); - } - } tokenType = scanner.Tag; return true; } @@ -72,17 +128,17 @@ namespace Implab.Formats.Json { /// true - чтение произведено успешно. false - достигнут конец входных данных /// В случе если токен не распознается, возникает исключение. Значения токенов обрабатываются, т.е. /// в строках обрабатываются экранированные символы, числа становтся типа double. - public bool ReadToken(out object tokenValue, out JsonTokenType tokenType) { + public bool ReadToken(out string tokenValue, out JsonTokenType tokenType) { JsonGrammar.TokenType tag; m_tokenBuilder.Clear(); - while (Read(m_jsonContext, out tag)) { + while (ReadChunk(m_jsonContext, out tag)) { switch (tag) { case JsonGrammar.TokenType.StringBound: tokenValue = ReadString(); tokenType = JsonTokenType.String; break; case JsonGrammar.TokenType.Number: - tokenValue = Double.Parse(m_tokenBuilder.ToString(), CultureInfo.InvariantCulture); + tokenValue = m_tokenBuilder.ToString(); tokenType = JsonTokenType.Number; break; case JsonGrammar.TokenType.Literal: @@ -108,7 +164,7 @@ namespace Implab.Formats.Json { JsonGrammar.TokenType tag; m_tokenBuilder.Clear(); - while (Read(m_stringContext, out tag)) { + while (ReadStringChunk(m_stringContext, out tag)) { switch (tag) { case JsonGrammar.TokenType.StringBound: m_tokenBuilder.Length--; diff --git a/Implab/Formats/JSON/JsonTextScanner.cs b/Implab/Formats/JSON/JsonTextScanner.cs --- a/Implab/Formats/JSON/JsonTextScanner.cs +++ b/Implab/Formats/JSON/JsonTextScanner.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Implab.Formats.Json { public class JsonTextScanner : JsonScanner { - const int _bufferSize = 4096; + const int _bufferSize = 16*4096; readonly TextReader m_reader; JsonTextScanner(TextReader reader, char[] buffer) : base(buffer, 0, 0) { diff --git a/Implab/Implab.csproj b/Implab/Implab.csproj --- a/Implab/Implab.csproj +++ b/Implab/Implab.csproj @@ -70,6 +70,7 @@ + @@ -171,7 +172,7 @@ - + @@ -199,6 +200,8 @@ + + diff --git a/Implab/Xml/JsonXmlReader.cs b/Implab/Xml/JsonXmlReader.cs --- a/Implab/Xml/JsonXmlReader.cs +++ b/Implab/Xml/JsonXmlReader.cs @@ -12,7 +12,7 @@ namespace Implab.Xml { public bool skip; } - JsonParser m_parser; + JsonReader m_parser; JsonXmlReaderOptions m_options; JsonXmlReaderPosition m_position = JsonXmlReaderPosition.Initial; XmlNameTable m_nameTable; @@ -57,7 +57,7 @@ namespace Implab.Xml { readonly string m_xsiNamespace; - public JsonXmlReader(JsonParser parser, JsonXmlReaderOptions options) { + public JsonXmlReader(JsonReader parser, JsonXmlReaderOptions options) { Safe.ArgumentNotNull(parser, nameof(parser)); m_parser = parser; @@ -77,7 +77,7 @@ namespace Implab.Xml { // TODO validate m_jsonRootName, m_jsonArrayItemName - m_context = new XmlNameContext(null); + m_context = new XmlNameContext(null, 0); } public override int AttributeCount { @@ -314,22 +314,6 @@ namespace Implab.Xml { case XmlNodeType.Element: // if the elemnt is empty the next element will be it's sibling return m_isEmpty; - - case XmlNodeType.Document: - case XmlNodeType.DocumentFragment: - case XmlNodeType.Entity: - case XmlNodeType.Text: - case XmlNodeType.CDATA: - case XmlNodeType.EntityReference: - case XmlNodeType.ProcessingInstruction: - case XmlNodeType.Comment: - case XmlNodeType.DocumentType: - case XmlNodeType.Notation: - case XmlNodeType.Whitespace: - case XmlNodeType.SignificantWhitespace: - case XmlNodeType.EndElement: - case XmlNodeType.EndEntity: - case XmlNodeType.XmlDeclaration: default: return true; } @@ -351,25 +335,29 @@ namespace Implab.Xml { if (!IsSibling()) // the node is nested m_xmlDepth++; - m_context = new XmlNameContext(m_context); + var context = m_context; List definedAttrs = null; // define new namespaces if (attrs != null) { foreach (var attr in attrs) { if (attr.QName.Name == "xmlns") { - m_context.DefinePrefix(ConvertValueToString(attr.Value), string.Empty); + if (context == m_context) + context = new XmlNameContext(m_context, m_xmlDepth); + context.DefinePrefix(ConvertValueToString(attr.Value), string.Empty); } else if (attr.Prefix == m_xmlnsPrefix) { - m_context.DefinePrefix(ConvertValueToString(attr.Value), attr.QName.Name); + if (context == m_context) + context = new XmlNameContext(m_context, m_xmlDepth); + context.DefinePrefix(ConvertValueToString(attr.Value), attr.QName.Name); } else { string attrPrefix; if (string.IsNullOrEmpty(attr.QName.Namespace)) continue; // auto-define prefixes - if (!m_context.LookupNamespacePrefix(attr.QName.Namespace, out attrPrefix) || string.IsNullOrEmpty(attrPrefix)) { + if (!context.LookupNamespacePrefix(attr.QName.Namespace, out attrPrefix) || string.IsNullOrEmpty(attrPrefix)) { // new namespace prefix added - attrPrefix = m_context.CreateNamespacePrefix(attr.QName.Namespace); + attrPrefix = context.CreateNamespacePrefix(attr.QName.Namespace); attr.Prefix = attrPrefix; if (definedAttrs == null) @@ -383,8 +371,10 @@ namespace Implab.Xml { string p; // auto-define prefixes - if (!m_context.LookupNamespacePrefix(ns, out p)) { - p = m_context.CreateNamespacePrefix(ns); + if (!context.LookupNamespacePrefix(ns, out p)) { + if (context == m_context) + context = new XmlNameContext(m_context, m_xmlDepth); + p = context.CreateNamespacePrefix(ns); if (definedAttrs == null) definedAttrs = new List(); @@ -397,6 +387,9 @@ namespace Implab.Xml { attrs = definedAttrs.ToArray(); } + if (!empty) + m_context = context; + m_nodeType = XmlNodeType.Element; m_qName = new XmlQualifiedName(name, ns); m_prefix = p; @@ -406,14 +399,18 @@ namespace Implab.Xml { } void EndElementNode(string name, string ns) { - if (IsSibling()) // closing the element which has children + if (IsSibling()) { + // closing the element which has children m_xmlDepth--; + } string p; if (!m_context.LookupNamespacePrefix(ns, out p)) throw new Exception($"Failed to lookup namespace '{ns}'"); - m_context = m_context.ParentContext; + if (m_context.Depth == m_xmlDepth) + m_context = m_context.ParentContext; + m_nodeType = XmlNodeType.EndElement; m_prefix = p; m_qName = new XmlQualifiedName(name, ns); @@ -456,7 +453,10 @@ namespace Implab.Xml { break; case JsonXmlReaderPosition.ValueElement: if (!m_isEmpty) { - ValueNode(m_parser.ElementValue); + if (m_parser.ElementValue != null && !m_parser.ElementValue.Equals(string.Empty)) + ValueNode(m_parser.ElementValue); + else + goto case JsonXmlReaderPosition.ValueContent; m_position = JsonXmlReaderPosition.ValueContent; return true; } else { @@ -521,7 +521,7 @@ namespace Implab.Xml { true ); else - ElementNode(m_jsonValueName, m_jsonNamespace, elementAttrs, m_parser.ElementValue as string == string.Empty); + ElementNode(m_jsonValueName, m_jsonNamespace, elementAttrs, m_parser.ElementValue.Equals(string.Empty)); break; default: throw new Exception($"Unexpected JSON element {m_parser.ElementType}: {m_parser.ElementName}"); diff --git a/Implab/Xml/JsonXmlReaderPosition.cs b/Implab/Xml/JsonXmlReaderPosition.cs --- a/Implab/Xml/JsonXmlReaderPosition.cs +++ b/Implab/Xml/JsonXmlReaderPosition.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; namespace Implab.Xml { - public enum JsonXmlReaderPosition { + enum JsonXmlReaderPosition { Initial, Declaration, BeginArray, diff --git a/Implab/Xml/SerializationHelpers.cs b/Implab/Xml/SerializationHelpers.cs new file mode 100644 --- /dev/null +++ b/Implab/Xml/SerializationHelpers.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; + +namespace Implab.Xml { + public static class SerializationHelpers { + public static string SerializeAsString(T obj) { + return SerializersPool.Instance.SerializeAsString(obj); + } + + public static void Serialize(XmlWriter writer, T obj) { + SerializersPool.Instance.Serialize(writer, obj); + } + + public static XmlDocument SerializeAsXmlDocument(T obj) { + var doc = new XmlDocument(); + using (var writer = doc.CreateNavigator().AppendChild()) { + SerializersPool.Instance.Serialize(writer, obj); + } + return doc; + } + + public static XDocument SerializeAsXDocument(T obj) { + var doc = new XDocument(); + using (var writer = doc.CreateWriter()) { + SerializersPool.Instance.Serialize(writer, obj); + } + return doc; + } + + public static T DeserializeFromString(string data) { + return SerializersPool.Instance.DeserializeFromString(data); + } + + public static T DeserializeFromXmlNode(XmlNode node) { + Safe.ArgumentNotNull(node, nameof(node)); + using (var reader = node.CreateNavigator().ReadSubtree()) + return SerializersPool.Instance.Deserialize(reader); + } + } +} diff --git a/Implab/Xml/SerializersPool.cs b/Implab/Xml/SerializersPool.cs new file mode 100644 --- /dev/null +++ b/Implab/Xml/SerializersPool.cs @@ -0,0 +1,76 @@ +using Implab.Components; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace Implab.Xml { + public class SerializersPool : ObjectPool { + + static readonly SerializersPool _instance = new SerializersPool(); + + public static SerializersPool Instance { + get { return _instance; } + } + + #region implemented abstract members of ObjectPool + protected override XmlSerializer CreateInstance() { + return new XmlSerializer(typeof(T)); + } + #endregion + + public T DeserializeFromString(string data) { + using (var reader = new StringReader(data)) { + return Deserialize(reader); + } + } + + public T Deserialize(TextReader reader) { + var sr = Allocate(); + try { + return (T)sr.Deserialize(reader); + } finally { + Release(sr); + } + } + + public T Deserialize(XmlReader reader) { + var sr = Allocate(); + try { + return (T)sr.Deserialize(reader); + } finally { + Release(sr); + } + } + + public string SerializeAsString(T data) { + using (var writer = new StringWriter()) { + Serialize(writer, data); + return writer.ToString(); + } + } + + public void Serialize(TextWriter writer, T data) { + var sr = Allocate(); + try { + sr.Serialize(writer, data); + } finally { + Release(sr); + } + } + + public void Serialize(XmlWriter writer, T data) { + var sr = Allocate(); + try { + sr.Serialize(writer, data); + } finally { + Release(sr); + } + } + + } +} diff --git a/Implab/Xml/XmlNameContext.cs b/Implab/Xml/XmlNameContext.cs --- a/Implab/Xml/XmlNameContext.cs +++ b/Implab/Xml/XmlNameContext.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using System.Xml; namespace Implab.Xml { - public class XmlNameContext { + class XmlNameContext { public const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/"; public const string XmlnsPrefix = "xmlns"; public const string XmlNamespace = "http://www.w3.org/XML/1998/namespace"; @@ -19,11 +19,17 @@ namespace Implab.Xml { Dictionary m_ns2prefix; Dictionary m_prefix2ns; int m_nextPrefix = 1; + string m_lastNs; + string m_lastPrefix; public XmlNameContext ParentContext { get; private set; } - public XmlNameContext(XmlNameContext parent) { + public int Depth { get; private set; } + + public XmlNameContext(XmlNameContext parent, int depth) { ParentContext = parent; + Depth = depth; + if (parent == null) { DefinePrefixNoCheck(XmlnsNamespace, XmlnsPrefix); DefinePrefixNoCheck(XmlNamespace, XmlPrefix); @@ -35,12 +41,17 @@ namespace Implab.Xml { public bool LookupNamespacePrefix(string ns, out string prefix) { if (ns == null) ns = string.Empty; + if (ns == m_lastNs) { + prefix = m_lastPrefix; + return true; + } + prefix = null; for (var ctx = this; ctx != null; ctx = ctx.ParentContext) { - if (ctx.m_ns2prefix?.TryGetValue(ns, out prefix) == true) { - if (ctx != this) // cache for the future use - DefinePrefixNoCheck(ns, prefix); + if (ctx.m_ns2prefix != null && ctx.m_ns2prefix.TryGetValue(ns, out prefix)) { + m_lastNs = ns; + m_lastPrefix = prefix; return true; } } @@ -88,11 +99,8 @@ namespace Implab.Xml { prefix = string.Empty; string ns = null; for(var ctx = this; ctx != null; ctx = ctx.ParentContext) { - if (ctx.m_prefix2ns?.TryGetValue(prefix, out ns) == true) { - if (ctx != this) // cache for the future use - DefinePrefixNoCheck(ns, prefix); + if (ctx.m_prefix2ns != null && ctx.m_prefix2ns.TryGetValue(prefix, out ns) == true) return ns; - } } return null; }