Mercurial > pub > ImplabNet
changeset 229:5f7a3e1d32b9 v2
JsonXmlReader performance tuning
JsonScanner now operates strings and doesn't
parses number and literals.
Added SerializationHelpers to common serialize/deserialize operations
author | cin |
---|---|
date | Tue, 12 Sep 2017 19:07:42 +0300 |
parents | 6fa235c5a760 |
children | 3e26338eb977 3eaa9372c754 d6fe09f5592c |
files | .hgignore Implab.Format.Test/JsonTests.cs Implab.Playground/App.config Implab.Playground/Implab.Playground.csproj Implab.Playground/Program.cs Implab.Playground/Properties/AssemblyInfo.cs Implab.Playground2.psess Implab.sln Implab/Automaton/DFATable.cs Implab/Formats/InputScanner.cs Implab/Formats/JSON/JsonParser.cs Implab/Formats/JSON/JsonReader.cs Implab/Formats/JSON/JsonScanner.cs Implab/Formats/JSON/JsonTextScanner.cs Implab/Implab.csproj Implab/Xml/JsonXmlReader.cs Implab/Xml/JsonXmlReaderPosition.cs Implab/Xml/SerializationHelpers.cs Implab/Xml/SerializersPool.cs Implab/Xml/XmlNameContext.cs |
diffstat | 20 files changed, 839 insertions(+), 394 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Tue Sep 12 01:19:12 2017 +0300 +++ b/.hgignore Tue Sep 12 19:07:42 2017 +0300 @@ -21,3 +21,5 @@ Implab.Format.Test/bin/ Implab.Format.Test/obj/ packages/ +Implab.Playground/obj/ +Implab.Playground/bin/
--- a/Implab.Format.Test/JsonTests.cs Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab.Format.Test/JsonTests.cs Tue Sep 12 19:07:42 2017 +0300 @@ -5,6 +5,7 @@ using System.Xml; using Implab.Formats; using Implab.Formats.Json; +using System.IO; namespace Implab.Format.Test { [TestFixture] @@ -15,19 +16,19 @@ using (var scanner = JsonStringScanner.Create(@"9123, -123, 0, 0.1, -0.2, -0.1e3, 1.3E-3, ""some \t\n\u0020 text"", literal []{}:")) { Tuple<JsonTokenType, object>[] expexted = { - new Tuple<JsonTokenType,object>(JsonTokenType.Number, 9123d), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "9123"), new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, -123d), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-123"), new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, 0d), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0"), new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, 0.1d), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0.1"), new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, -0.2d), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.2"), new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, -0.1e3d), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.1e3"), new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), - new Tuple<JsonTokenType,object>(JsonTokenType.Number, 1.3E-3d), + new Tuple<JsonTokenType,object>(JsonTokenType.Number, "1.3E-3"), new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), new Tuple<JsonTokenType,object>(JsonTokenType.String, "some \t\n text"), new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null), @@ -39,7 +40,7 @@ new Tuple<JsonTokenType,object>(JsonTokenType.NameSeparator, null) }; - object value; + string value; JsonTokenType tokenType; for (var i = 0; i < expexted.Length; i++) { @@ -73,7 +74,7 @@ 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 @@ //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 @@ 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 @@ 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); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Playground/App.config Tue Sep 12 19:07:42 2017 +0300 @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> + </startup> +</configuration> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Playground/Implab.Playground.csproj Tue Sep 12 19:07:42 2017 +0300 @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{100DFEB0-75BE-436F-ADDF-1F46EF433F46}</ProjectGuid> + <OutputType>Exe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Implab.Playground</RootNamespace> + <AssemblyName>Implab.Playground</AssemblyName> + <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <Prefer32Bit>true</Prefer32Bit> + <DebugSymbols>true</DebugSymbols> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Implab\Implab.csproj"> + <Project>{f550f1f8-8746-4ad0-9614-855f4c4b7f05}</Project> + <Name>Implab</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Playground/Program.cs Tue Sep 12 19:07:42 2017 +0300 @@ -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<XmlSimpleModel>(doc.DocumentElement); + + Console.WriteLine("done"); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Playground/Properties/AssemblyInfo.cs Tue Sep 12 19:07:42 2017 +0300 @@ -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")]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Playground2.psess Tue Sep 12 19:07:42 2017 +0300 @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VSPerformanceSession Version="1.00"> + <Options> + <Solution>Implab.sln</Solution> + <CollectionMethod>Sampling</CollectionMethod> + <AllocationMethod>None</AllocationMethod> + <AddReport>true</AddReport> + <ResourceBasedAnalysisSelected>true</ResourceBasedAnalysisSelected> + <UniqueReport>Timestamp</UniqueReport> + <SamplingMethod>Cycles</SamplingMethod> + <CycleCount>50000</CycleCount> + <PageFaultCount>10</PageFaultCount> + <SysCallCount>10</SysCallCount> + <SamplingCounter Name="" ReloadValue="00000000000f4240" DisplayName="" /> + <RelocateBinaries>false</RelocateBinaries> + <HardwareCounters EnableHWCounters="false" /> + <EtwSettings /> + <PdhSettings> + <PdhCountersEnabled>false</PdhCountersEnabled> + <PdhCountersRate>500</PdhCountersRate> + <PdhCounters> + <PdhCounter>\Память\Обмен страниц/с</PdhCounter> + <PdhCounter>\Процессор(_Total)\% загруженности процессора</PdhCounter> + <PdhCounter>\Физический диск(_Total)\Средняя длина очереди диска</PdhCounter> + </PdhCounters> + </PdhSettings> + </Options> + <ExcludeSmallFuncs>true</ExcludeSmallFuncs> + <InteractionProfilingEnabled>false</InteractionProfilingEnabled> + <JScriptProfilingEnabled>false</JScriptProfilingEnabled> + <PreinstrumentEvent> + <InstrEventExclude>false</InstrEventExclude> + </PreinstrumentEvent> + <PostinstrumentEvent> + <InstrEventExclude>false</InstrEventExclude> + </PostinstrumentEvent> + <Binaries> + <ProjBinary> + <Path>Implab.Playground\obj\Debug\Implab.Playground.exe</Path> + <ArgumentTimestamp>01/01/0001 00:00:00</ArgumentTimestamp> + <Instrument>true</Instrument> + <Sample>true</Sample> + <ExternalWebsite>false</ExternalWebsite> + <InteractionProfilingEnabled>false</InteractionProfilingEnabled> + <IsLocalJavascript>false</IsLocalJavascript> + <IsWindowsStoreApp>false</IsWindowsStoreApp> + <IsWWA>false</IsWWA> + <LaunchProject>true</LaunchProject> + <OverrideProjectSettings>false</OverrideProjectSettings> + <LaunchMethod>Executable</LaunchMethod> + <ExecutablePath>Implab.Playground\bin\Release\Implab.Playground.exe</ExecutablePath> + <StartupDirectory>Implab.Playground\bin\Release\</StartupDirectory> + <Arguments> + </Arguments> + <NetAppHost>IIS</NetAppHost> + <NetBrowser>InternetExplorer</NetBrowser> + <ExcludeSmallFuncs>true</ExcludeSmallFuncs> + <JScriptProfilingEnabled>false</JScriptProfilingEnabled> + <PreinstrumentEvent> + <InstrEventExclude>false</InstrEventExclude> + </PreinstrumentEvent> + <PostinstrumentEvent> + <InstrEventExclude>false</InstrEventExclude> + </PostinstrumentEvent> + <ProjRef>{100DFEB0-75BE-436F-ADDF-1F46EF433F46}|Implab.Playground\Implab.Playground.csproj</ProjRef> + <ProjPath>Implab.Playground\Implab.Playground.csproj</ProjPath> + <ProjName>Implab.Playground</ProjName> + </ProjBinary> + </Binaries> + <Launches> + <ProjBinary> + <Path>:PB:{100DFEB0-75BE-436F-ADDF-1F46EF433F46}|Implab.Playground\Implab.Playground.csproj</Path> + </ProjBinary> + </Launches> +</VSPerformanceSession> \ No newline at end of file
--- a/Implab.sln Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab.sln Tue Sep 12 19:07:42 2017 +0300 @@ -16,11 +16,14 @@ 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 @@ {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 @@ {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
--- a/Implab/Automaton/DFATable.cs Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab/Automaton/DFATable.cs Tue Sep 12 19:07:42 2017 +0300 @@ -119,7 +119,7 @@ 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; }
--- a/Implab/Formats/InputScanner.cs Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab/Formats/InputScanner.cs Tue Sep 12 19:07:42 2017 +0300 @@ -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 @@ } 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 @@ 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) {
--- a/Implab/Formats/JSON/JsonParser.cs Tue Sep 12 01:19:12 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,294 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using Implab.Automaton; -using Implab.Automaton.RegularExpressions; -using System.Linq; -using Implab.Components; -using System.Collections.Generic; - -namespace Implab.Formats.Json { - /// <summary> - /// Pull парсер JSON данных. - /// </summary> - /// <remarks> - /// Следует отметить отдельную интерпретацию свойства <see cref="Level"/>, - /// оно означает текущий уровень вложенности объектов, однако закрывающий - /// элемент объекта и массива имеет уровень меньше, чем сам объект. - /// <code> - /// { // Level = 1 - /// "name" : "Peter", // Level = 1 - /// "address" : { // Level = 2 - /// city : "Stern" // Level = 2 - /// } // Level = 1 - /// } // Level = 0 - /// </code> - /// </remarks> - public class JsonParser : Disposable { - - enum MemberContext { - MemberName, - MemberValue - } - - #region Parser rules - struct ParserContext { - readonly int[,] m_dfa; - int m_state; - - readonly JsonElementContext m_elementContext; - - public ParserContext(int[,] dfa, int state, JsonElementContext context) { - m_dfa = dfa; - m_state = state; - m_elementContext = context; - } - - public bool Move(JsonTokenType token) { - var next = m_dfa[m_state, (int)token]; - if (next == AutomatonConst.UNREACHABLE_STATE) - return false; - m_state = next; - return true; - } - - public JsonElementContext ElementContext { - get { return m_elementContext; } - } - } - - static readonly ParserContext _jsonContext; - static readonly ParserContext _objectContext; - static readonly ParserContext _arrayContext; - - static JsonParser() { - - var valueExpression = MakeToken(JsonTokenType.BeginArray, JsonTokenType.BeginObject, JsonTokenType.Literal, JsonTokenType.Number, JsonTokenType.String); - var memberExpression = MakeToken(JsonTokenType.String).Cat(MakeToken(JsonTokenType.NameSeparator)).Cat(valueExpression); - - var objectExpression = memberExpression - .Cat( - MakeToken(JsonTokenType.ValueSeparator) - .Cat(memberExpression) - .EClosure() - ) - .Optional() - .Cat(MakeToken(JsonTokenType.EndObject)) - .End(); - - var arrayExpression = valueExpression - .Cat( - MakeToken(JsonTokenType.ValueSeparator) - .Cat(valueExpression) - .EClosure() - ) - .Optional() - .Cat(MakeToken(JsonTokenType.EndArray)) - .End(); - - var jsonExpression = valueExpression.End(); - - _jsonContext = CreateParserContext(jsonExpression, JsonElementContext.None); - _objectContext = CreateParserContext(objectExpression, JsonElementContext.Object); - _arrayContext = CreateParserContext(arrayExpression, JsonElementContext.Array); - } - - static Token MakeToken(params JsonTokenType[] input) { - return Token.New( input.Select(t => (int)t).ToArray() ); - } - - static ParserContext CreateParserContext(Token expr, JsonElementContext context) { - - var dfa = new DFATable(); - var builder = new RegularExpressionVisitor(dfa); - expr.Accept(builder); - builder.BuildDFA(); - - return new ParserContext(dfa.CreateTransitionTable(), dfa.InitialState, context); - } - - #endregion - - readonly JsonScanner m_scanner; - // json starts from the value context and may content even a single literal - MemberContext m_memberContext = MemberContext.MemberValue; - - JsonElementType m_elementType; - object m_elementValue; - string m_memberName = String.Empty; - - Stack<ParserContext> m_stack = new Stack<ParserContext>(); - ParserContext m_context = _jsonContext; - - /// <summary> - /// Создает новый парсер на основе строки, содержащей JSON - /// </summary> - /// <param name="text"></param> - public JsonParser(string text) { - Safe.ArgumentNotEmpty(text, "text"); - m_scanner = JsonStringScanner.Create(text); - } - - /// <summary> - /// Создает новый экземпляр парсера, на основе текстового потока. - /// </summary> - /// <param name="reader">Текстовый поток.</param> - public JsonParser(TextReader reader) { - Safe.ArgumentNotNull(reader, "reader"); - m_scanner = JsonTextScanner.Create(reader); - } - - public int Level { - get { return m_stack.Count; } - } - - /// <summary> - /// Тип текущего элемента на котором стоит парсер. - /// </summary> - public JsonElementType ElementType { - get { return m_elementType; } - } - - /// <summary> - /// Имя элемента - имя свойства родительского контейнера. Для элементов массивов и корневого всегда - /// пустая строка. - /// </summary> - public string ElementName { - get { return m_memberName; } - } - - /// <summary> - /// Значение элемента. Только для элементов типа <see cref="JsonElementType.Value"/>, для остальных <c>null</c> - /// </summary> - public object ElementValue { - get { return m_elementValue; } - } - - /// <summary> - /// Читает слеюудущий объект из потока - /// </summary> - /// <returns><c>true</c> - операция чтения прошла успешно, <c>false</c> - конец данных</returns> - public bool Read() { - object tokenValue; - JsonTokenType tokenType; - - m_memberName = String.Empty; - - while (m_scanner.ReadToken(out tokenValue, out tokenType)) { - if(!m_context.Move(tokenType)) - UnexpectedToken(tokenValue, tokenType); - - switch (tokenType) { - case JsonTokenType.BeginObject: - m_stack.Push(m_context); - m_context = _objectContext; - - m_elementValue = null; - m_memberContext = MemberContext.MemberName; - m_elementType = JsonElementType.BeginObject; - return true; - case JsonTokenType.EndObject: - if (m_stack.Count == 0) - UnexpectedToken(tokenValue, tokenType); - m_context = m_stack.Pop(); - - m_elementValue = null; - m_elementType = JsonElementType.EndObject; - return true; - case JsonTokenType.BeginArray: - m_stack.Push(m_context); - m_context = _arrayContext; - - m_elementValue = null; - m_memberContext = MemberContext.MemberValue; - m_elementType = JsonElementType.BeginArray; - return true; - case JsonTokenType.EndArray: - if (m_stack.Count == 0) - UnexpectedToken(tokenValue, tokenType); - m_context = m_stack.Pop(); - - m_elementValue = null; - m_elementType = JsonElementType.EndArray; - return true; - case JsonTokenType.String: - if (m_memberContext == MemberContext.MemberName) { - m_memberName = (string)tokenValue; - break; - } - m_elementType = JsonElementType.Value; - m_elementValue = tokenValue; - return true; - case JsonTokenType.Number: - m_elementType = JsonElementType.Value; - m_elementValue = tokenValue; - return true; - case JsonTokenType.Literal: - m_elementType = JsonElementType.Value; - m_elementValue = ParseLiteral((string)tokenValue); - return true; - case JsonTokenType.NameSeparator: - m_memberContext = MemberContext.MemberValue; - break; - case JsonTokenType.ValueSeparator: - m_memberContext = m_context.ElementContext == JsonElementContext.Object ? MemberContext.MemberName : MemberContext.MemberValue; - break; - default: - UnexpectedToken(tokenValue, tokenType); - break; - } - } - if (m_context.ElementContext != JsonElementContext.None) - throw new ParserException("Unexpedted end of data"); - - EOF = true; - - return false; - } - - object ParseLiteral(string literal) { - switch (literal) { - case "null": - return null; - case "false": - return false; - case "true": - return true; - default: - UnexpectedToken(literal, JsonTokenType.Literal); - return null; // avoid compliler error - } - } - - void UnexpectedToken(object value, JsonTokenType tokenType) { - throw new ParserException(String.Format("Unexpected token {0}: '{1}'", tokenType, value)); - } - - - /// <summary> - /// Признак конца потока - /// </summary> - public bool EOF { - get; - private set; - } - - protected override void Dispose(bool disposing) { - if (disposing) - m_scanner.Dispose(); - } - - /// <summary> - /// Переходит в конец текущего объекта. - /// </summary> - public void SeekElementEnd() { - var level = Level - 1; - - Debug.Assert(level >= 0); - - while (Level != level) - Read(); - } - } - -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Formats/JSON/JsonReader.cs Tue Sep 12 19:07:42 2017 +0300 @@ -0,0 +1,318 @@ +using System; +using System.Diagnostics; +using System.IO; +using Implab.Automaton; +using Implab.Automaton.RegularExpressions; +using System.Linq; +using Implab.Components; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace Implab.Formats.Json { + /// <summary> + /// Pull парсер JSON данных. + /// </summary> + /// <remarks> + /// Следует отметить отдельную интерпретацию свойства <see cref="Level"/>, + /// оно означает текущий уровень вложенности объектов, однако закрывающий + /// элемент объекта и массива имеет уровень меньше, чем сам объект. + /// <code> + /// { // Level = 1 + /// "name" : "Peter", // Level = 1 + /// "address" : { // Level = 2 + /// city : "Stern" // Level = 2 + /// } // Level = 1 + /// } // Level = 0 + /// </code> + /// </remarks> + public class JsonReader : Disposable { + + enum MemberContext { + MemberName, + MemberValue + } + + #region Parser rules + struct ParserContext { + readonly int[,] m_dfa; + int m_state; + + readonly JsonElementContext m_elementContext; + + public ParserContext(int[,] dfa, int state, JsonElementContext context) { + m_dfa = dfa; + m_state = state; + m_elementContext = context; + } + + public bool Move(JsonTokenType token) { + var next = m_dfa[m_state, (int)token]; + if (next == AutomatonConst.UNREACHABLE_STATE) + return false; + m_state = next; + return true; + } + + public JsonElementContext ElementContext { + get { return m_elementContext; } + } + } + + static readonly ParserContext _jsonContext; + static readonly ParserContext _objectContext; + static readonly ParserContext _arrayContext; + + 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); + + var objectExpression = memberExpression + .Cat( + MakeToken(JsonTokenType.ValueSeparator) + .Cat(memberExpression) + .EClosure() + ) + .Optional() + .Cat(MakeToken(JsonTokenType.EndObject)) + .End(); + + var arrayExpression = valueExpression + .Cat( + MakeToken(JsonTokenType.ValueSeparator) + .Cat(valueExpression) + .EClosure() + ) + .Optional() + .Cat(MakeToken(JsonTokenType.EndArray)) + .End(); + + var jsonExpression = valueExpression.End(); + + _jsonContext = CreateParserContext(jsonExpression, JsonElementContext.None); + _objectContext = CreateParserContext(objectExpression, JsonElementContext.Object); + _arrayContext = CreateParserContext(arrayExpression, JsonElementContext.Array); + } + + static Token MakeToken(params JsonTokenType[] input) { + return Token.New( input.Select(t => (int)t).ToArray() ); + } + + static ParserContext CreateParserContext(Token expr, JsonElementContext context) { + + var dfa = new DFATable(); + var builder = new RegularExpressionVisitor(dfa); + expr.Accept(builder); + builder.BuildDFA(); + + return new ParserContext(dfa.CreateTransitionTable(), dfa.InitialState, context); + } + + #endregion + + readonly JsonScanner m_scanner; + // json starts from the value context and may content even a single literal + MemberContext m_memberContext = MemberContext.MemberValue; + + JsonElementType m_elementType; + object m_elementValue; + string m_memberName = String.Empty; + + Stack<ParserContext> m_stack = new Stack<ParserContext>(); + ParserContext m_context = _jsonContext; + + /// <summary> + /// Создает новый парсер на основе строки, содержащей JSON + /// </summary> + /// <param name="text"></param> + JsonReader(JsonScanner scanner) { + m_scanner = scanner; + } + + public int Level { + get { return m_stack.Count; } + } + + /// <summary> + /// Тип текущего элемента на котором стоит парсер. + /// </summary> + public JsonElementType ElementType { + get { return m_elementType; } + } + + /// <summary> + /// Имя элемента - имя свойства родительского контейнера. Для элементов массивов и корневого всегда + /// пустая строка. + /// </summary> + public string ElementName { + get { return m_memberName; } + } + + /// <summary> + /// Значение элемента. Только для элементов типа <see cref="JsonElementType.Value"/>, для остальных <c>null</c> + /// </summary> + public object ElementValue { + get { return m_elementValue; } + } + + /// <summary> + /// Читает слеюудущий объект из потока + /// </summary> + /// <returns><c>true</c> - операция чтения прошла успешно, <c>false</c> - конец данных</returns> + public bool Read() { + string tokenValue; + JsonTokenType tokenType; + + m_memberName = String.Empty; + + while (m_scanner.ReadToken(out tokenValue, out tokenType)) { + if(!m_context.Move(tokenType)) + UnexpectedToken(tokenValue, tokenType); + + switch (tokenType) { + case JsonTokenType.BeginObject: + m_stack.Push(m_context); + m_context = _objectContext; + + m_elementValue = null; + m_memberContext = MemberContext.MemberName; + m_elementType = JsonElementType.BeginObject; + return true; + case JsonTokenType.EndObject: + if (m_stack.Count == 0) + UnexpectedToken(tokenValue, tokenType); + m_context = m_stack.Pop(); + + m_elementValue = null; + m_elementType = JsonElementType.EndObject; + return true; + case JsonTokenType.BeginArray: + m_stack.Push(m_context); + m_context = _arrayContext; + + m_elementValue = null; + m_memberContext = MemberContext.MemberValue; + m_elementType = JsonElementType.BeginArray; + return true; + case JsonTokenType.EndArray: + if (m_stack.Count == 0) + UnexpectedToken(tokenValue, tokenType); + m_context = m_stack.Pop(); + + m_elementValue = null; + m_elementType = JsonElementType.EndArray; + return true; + case JsonTokenType.String: + if (m_memberContext == MemberContext.MemberName) { + m_memberName = tokenValue; + break; + } + m_elementType = JsonElementType.Value; + m_elementValue = tokenValue; + return true; + case JsonTokenType.Number: + m_elementType = JsonElementType.Value; + m_elementValue = double.Parse(tokenValue, CultureInfo.InvariantCulture); + return true; + case JsonTokenType.Literal: + m_elementType = JsonElementType.Value; + m_elementValue = ParseLiteral(tokenValue); + return true; + case JsonTokenType.NameSeparator: + m_memberContext = MemberContext.MemberValue; + break; + case JsonTokenType.ValueSeparator: + m_memberContext = m_context.ElementContext == JsonElementContext.Object ? MemberContext.MemberName : MemberContext.MemberValue; + break; + default: + UnexpectedToken(tokenValue, tokenType); + break; + } + } + if (m_context.ElementContext != JsonElementContext.None) + throw new ParserException("Unexpedted end of data"); + + Eof = true; + + return false; + } + + object ParseLiteral(string literal) { + switch (literal) { + case "null": + return null; + case "false": + return false; + case "true": + return true; + default: + UnexpectedToken(literal, JsonTokenType.Literal); + return null; // avoid compliler error + } + } + + void UnexpectedToken(object value, JsonTokenType tokenType) { + throw new ParserException(String.Format("Unexpected token {0}: '{1}'", tokenType, value)); + } + + + /// <summary> + /// Признак конца потока + /// </summary> + public bool Eof { + get; + private set; + } + + protected override void Dispose(bool disposing) { + if (disposing) + m_scanner.Dispose(); + } + + /// <summary> + /// Переходит в конец текущего объекта. + /// </summary> + public void SeekElementEnd() { + var level = Level - 1; + + Debug.Assert(level >= 0); + + 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)); + } + } + +}
--- a/Implab/Formats/JSON/JsonScanner.cs Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab/Formats/JSON/JsonScanner.cs Tue Sep 12 19:07:42 2017 +0300 @@ -25,38 +25,94 @@ m_length = length; } - bool Read(InputScanner<JsonGrammar.TokenType> scanner, out JsonGrammar.TokenType tokenType) { - scanner.Reset(); + bool ReadChunk(InputScanner<JsonGrammar.TokenType> 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<JsonGrammar.TokenType> 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 @@ /// <returns><c>true</c> - чтение произведено успешно. <c>false</c> - достигнут конец входных данных</returns> /// <remarks>В случе если токен не распознается, возникает исключение. Значения токенов обрабатываются, т.е. /// в строках обрабатываются экранированные символы, числа становтся типа double.</remarks> - 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 @@ 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--;
--- a/Implab/Formats/JSON/JsonTextScanner.cs Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab/Formats/JSON/JsonTextScanner.cs Tue Sep 12 19:07:42 2017 +0300 @@ -7,7 +7,7 @@ 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) {
--- a/Implab/Implab.csproj Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab/Implab.csproj Tue Sep 12 19:07:42 2017 +0300 @@ -70,6 +70,7 @@ <Reference Include="System" /> <Reference Include="System.Xml" /> <Reference Include="mscorlib" /> + <Reference Include="System.Xml.Linq" /> </ItemGroup> <ItemGroup> <Compile Include="Components\StateChangeEventArgs.cs" /> @@ -171,7 +172,7 @@ <Compile Include="Formats\Json\JsonElementContext.cs" /> <Compile Include="Formats\Json\JsonElementType.cs" /> <Compile Include="Formats\Json\JsonGrammar.cs" /> - <Compile Include="Formats\Json\JsonParser.cs" /> + <Compile Include="Formats\Json\JsonReader.cs" /> <Compile Include="Formats\Json\JsonScanner.cs" /> <Compile Include="Formats\Json\JsonTokenType.cs" /> <Compile Include="Formats\Json\JsonWriter.cs" /> @@ -199,6 +200,8 @@ <Compile Include="Xml\JsonXmlReader.cs" /> <Compile Include="Xml\JsonXmlReaderOptions.cs" /> <Compile Include="Xml\JsonXmlReaderPosition.cs" /> + <Compile Include="Xml\SerializationHelpers.cs" /> + <Compile Include="Xml\SerializersPool.cs" /> <Compile Include="Xml\XmlSimpleAttribute.cs" /> <Compile Include="Xml\XmlNameContext.cs" /> </ItemGroup>
--- a/Implab/Xml/JsonXmlReader.cs Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab/Xml/JsonXmlReader.cs Tue Sep 12 19:07:42 2017 +0300 @@ -12,7 +12,7 @@ public bool skip; } - JsonParser m_parser; + JsonReader m_parser; JsonXmlReaderOptions m_options; JsonXmlReaderPosition m_position = JsonXmlReaderPosition.Initial; XmlNameTable m_nameTable; @@ -57,7 +57,7 @@ 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 @@ // 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 @@ 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 @@ if (!IsSibling()) // the node is nested m_xmlDepth++; - m_context = new XmlNameContext(m_context); + var context = m_context; List<XmlSimpleAttribute> 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 @@ 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<XmlSimpleAttribute>(); @@ -397,6 +387,9 @@ 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 @@ } 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 @@ 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 @@ 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}");
--- a/Implab/Xml/JsonXmlReaderPosition.cs Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab/Xml/JsonXmlReaderPosition.cs Tue Sep 12 19:07:42 2017 +0300 @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Implab.Xml { - public enum JsonXmlReaderPosition { + enum JsonXmlReaderPosition { Initial, Declaration, BeginArray,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Xml/SerializationHelpers.cs Tue Sep 12 19:07:42 2017 +0300 @@ -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>(T obj) { + return SerializersPool<T>.Instance.SerializeAsString(obj); + } + + public static void Serialize<T>(XmlWriter writer, T obj) { + SerializersPool<T>.Instance.Serialize(writer, obj); + } + + public static XmlDocument SerializeAsXmlDocument<T>(T obj) { + var doc = new XmlDocument(); + using (var writer = doc.CreateNavigator().AppendChild()) { + SerializersPool<T>.Instance.Serialize(writer, obj); + } + return doc; + } + + public static XDocument SerializeAsXDocument<T>(T obj) { + var doc = new XDocument(); + using (var writer = doc.CreateWriter()) { + SerializersPool<T>.Instance.Serialize(writer, obj); + } + return doc; + } + + public static T DeserializeFromString<T>(string data) { + return SerializersPool<T>.Instance.DeserializeFromString(data); + } + + public static T DeserializeFromXmlNode<T>(XmlNode node) { + Safe.ArgumentNotNull(node, nameof(node)); + using (var reader = node.CreateNavigator().ReadSubtree()) + return SerializersPool<T>.Instance.Deserialize(reader); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Xml/SerializersPool.cs Tue Sep 12 19:07:42 2017 +0300 @@ -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<T> : ObjectPool<XmlSerializer> { + + static readonly SerializersPool<T> _instance = new SerializersPool<T>(); + + public static SerializersPool<T> 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); + } + } + + } +}
--- a/Implab/Xml/XmlNameContext.cs Tue Sep 12 01:19:12 2017 +0300 +++ b/Implab/Xml/XmlNameContext.cs Tue Sep 12 19:07:42 2017 +0300 @@ -6,7 +6,7 @@ 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 @@ Dictionary<string, string> m_ns2prefix; Dictionary<string, string> 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 @@ 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 @@ 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; }