Mercurial > pub > ImplabNet
changeset 264:3a6e18c432be v3
Added XmlToJson xsl transformation.
Added JsonXmlReader.CreateJsonXmlReader(...) methods
Added SerializationHelpers.SerializeJson/DeserializeJson methods
author | cin |
---|---|
date | Mon, 16 Apr 2018 18:43:49 +0300 |
parents | 711572866e0c |
children | 74e048cbaac8 |
files | Implab.Test/JsonTests.cs Implab.Test/Model/Person.cs Implab/Components/LazyAndWeak.cs Implab/Implab.csproj Implab/Xml/JsonXmlReader.cs Implab/Xml/SerializationHelpers.cs Implab/Xml/XmlToJson.cs Implab/Xml/json.xsl Implab/Xml/readme.txt |
diffstat | 9 files changed, 410 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/Implab.Test/JsonTests.cs Mon Apr 16 02:12:57 2018 +0300 +++ b/Implab.Test/JsonTests.cs Mon Apr 16 18:43:49 2018 +0300 @@ -6,6 +6,7 @@ using Implab.Formats; using Implab.Formats.Json; using System.IO; +using Implab.Test.Model; namespace Implab.Test { public class JsonTests { @@ -114,6 +115,44 @@ DumpJsonFlatParse("[1,2,\"\",[3,4],{\"info\": [5,6]},{\"num\": [7,8,null]}, null,[null]]"); } + [Fact] + public void XmlToJsonTransform() { + var person = new Person { + FirstName = "Charlie", + LastName = "Brown", + Age = 19, + AgeSpecified = true + }; + + var doc = SerializationHelpers.SerializeAsXmlDocument(person); + + using (var writer = new StringWriter()) { + XmlToJson.Default.Transform(doc,null, writer); + Console.WriteLine(writer.ToString()); + } + } + + [Fact] + public void JsonSerialization() { + var person = new Person { + FirstName = "Charlie", + LastName = "Brown", + Age = 19, + AgeSpecified = true, + Tags = new [] { "brave", "stupid" } + }; + + var data = SerializationHelpers.SerializeJsonAsString(person); + Console.WriteLine(data); + var clone = SerializationHelpers.DeserializeJsonFromString<Person>(data); + + Assert.Equal(person.FirstName, clone.FirstName); + Assert.Equal(person.LastName, clone.LastName); + Assert.Equal(person.Age, clone.Age); + Assert.Equal(person.AgeSpecified, clone.AgeSpecified); + Assert.Equal(person.Tags, person.Tags); + } + void AssertRead(XmlReader reader, XmlNodeType expected) { Assert.True(reader.Read()); Console.WriteLine($"{new string(' ', reader.Depth * 2)}{reader}");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab.Test/Model/Person.cs Mon Apr 16 18:43:49 2018 +0300 @@ -0,0 +1,20 @@ +using System.Xml.Serialization; + +namespace Implab.Test.Model { + + [XmlRoot(Namespace="urn:implab:test:model")] + public class Person { + public string FirstName { get; set; } + + public string LastName { get; set; } + + public int Age { get; set; } + + [XmlIgnore] + public bool AgeSpecified { get; set; } + + + [XmlElement("Tag")] + public string[] Tags { get; set; } + } +} \ No newline at end of file
--- a/Implab/Components/LazyAndWeak.cs Mon Apr 16 02:12:57 2018 +0300 +++ b/Implab/Components/LazyAndWeak.cs Mon Apr 16 18:43:49 2018 +0300 @@ -8,6 +8,7 @@ /// <remarks> /// Usefull when dealing with memory-intensive objects which are frequently used. /// This class is similar to <see cref="ObjectPool{T}"/> except it is a singleton. + /// This class can't be used to hold diposable objects. /// </remarks> public class LazyAndWeak<T> where T : class {
--- a/Implab/Implab.csproj Mon Apr 16 02:12:57 2018 +0300 +++ b/Implab/Implab.csproj Mon Apr 16 18:43:49 2018 +0300 @@ -19,4 +19,8 @@ <DefineConstants Condition="'$(TargetFramework)'=='net46'">NETFX_TRACE_BUG;$(DefineConstants)</DefineConstants> </PropertyGroup> + <ItemGroup> + <EmbeddedResource Include="Xml\json.xsl"/> + </ItemGroup> + </Project>
--- a/Implab/Xml/JsonXmlReader.cs Mon Apr 16 02:12:57 2018 +0300 +++ b/Implab/Xml/JsonXmlReader.cs Mon Apr 16 18:43:49 2018 +0300 @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Xml; @@ -588,6 +589,12 @@ return !skip; } + protected override void Dispose(bool disposing) { + if (disposing) + Safe.Dispose(m_parser); + base.Dispose(true); + } + public override string ToString() { switch (NodeType) { case XmlNodeType.Element: @@ -606,5 +613,24 @@ return $".{NodeType} {Name} {Value}"; } } + + #region static methods + + public static JsonXmlReader CreateJsonXmlReader(TextReader textReader, JsonXmlReaderOptions options = null) { + var jsonReader = JsonReader.Create(textReader); + return new JsonXmlReader(jsonReader, options); + } + + public static JsonXmlReader CreateJsonXmlReader(Stream stream, JsonXmlReaderOptions options = null) { + var jsonReader = JsonReader.Create(stream); + return new JsonXmlReader(jsonReader, options); + } + + public static JsonXmlReader CreateJsonXmlReader(string file, JsonXmlReaderOptions options = null) { + var jsonReader = JsonReader.Create(file); + return new JsonXmlReader(jsonReader, options); + } + + #endregion } }
--- a/Implab/Xml/SerializationHelpers.cs Mon Apr 16 02:12:57 2018 +0300 +++ b/Implab/Xml/SerializationHelpers.cs Mon Apr 16 18:43:49 2018 +0300 @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; +using System.Xml.Serialization; namespace Implab.Xml { public static class SerializationHelpers { @@ -61,5 +63,34 @@ using (var reader = node.CreateNavigator().ReadSubtree()) return SerializersPool<T>.Instance.Deserialize(reader); } + + public static T DeserializeJson<T>(TextReader textReader) { + var options = new JsonXmlReaderOptions { + NamespaceUri = typeof(T).GetCustomAttribute<XmlRootAttribute>()?.Namespace, + RootName = typeof(T).Name, + FlattenArrays = true + }; + + using(var reader = JsonXmlReader.CreateJsonXmlReader(textReader, options)) + return Deserialize<T>(reader); + } + + public static T DeserializeJsonFromString<T>(string data) { + using(var reader = new StringReader(data)) { + return DeserializeJson<T>(reader); + } + } + + public static void SerializeJson<T>(TextWriter writer, T obj) { + var doc = SerializeAsXmlDocument(obj); + XmlToJson.Default.Transform(doc, null, writer); + } + + public static string SerializeJsonAsString<T>(T obj) { + using(var writer = new StringWriter()) { + SerializeJson(writer, obj); + return writer.ToString(); + } + } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Xml/XmlToJson.cs Mon Apr 16 18:43:49 2018 +0300 @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Reflection; +using System.Xml; +using System.Xml.Xsl; +using Implab.Components; +using Implab.Formats.Json; + +namespace Implab.Xml { + public class XmlToJson { + const string XmlToJsonTransformId = "Implab.Xml.json.xsl"; + + static LazyAndWeak<XslCompiledTransform> m_default = new LazyAndWeak<XslCompiledTransform>(CreateTransform, true); + + public static XslCompiledTransform Default { + get { return m_default.Value; } + } + + protected static XslCompiledTransform CreateTransform() { + var transform = new XslCompiledTransform(); + using(var reader = XmlReader.Create(GetDefaultTransform())) { + transform.Load(reader); + } + return transform; + } + + protected static Stream GetDefaultTransform() { + return Assembly.GetExecutingAssembly().GetManifestResourceStream(XmlToJsonTransformId); + } + + + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Xml/json.xsl Mon Apr 16 18:43:49 2018 +0300 @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:exsl="http://exslt.org/common"> + <xsl:output method="text" /> + + <xsl:template match="/"> + <xsl:apply-templates mode="json-value" /> + </xsl:template> + + + <!-- handle json-object --> + + <xsl:template match="*" mode="json-object"> + <xsl:call-template name="write-members"/> + </xsl:template> + + <xsl:template match="*|@*" mode="json-member"> + <xsl:param name="values" select="."/> + <xsl:call-template name="write-string"> + <xsl:with-param name="text"><xsl:apply-templates select="." mode="json-member-name"/></xsl:with-param> + </xsl:call-template> + <xsl:text> : </xsl:text> + <xsl:apply-templates select="." mode="json-member-value"> + <xsl:with-param name="values" select="$values"/> + </xsl:apply-templates> + </xsl:template> + + <xsl:template match="*" mode="json-member-name"> + <xsl:value-of select="local-name(.)"/> + </xsl:template> + + <xsl:template match="@*" mode="json-member-name"> + <xsl:value-of select="concat('_',local-name(.))"/> + </xsl:template> + + <xsl:template match="*|@*" mode="json-member-value"> + <xsl:param name="values" select="."/> + <xsl:choose> + <xsl:when test="count($values) > 1"> + <xsl:call-template name="write-array"> + <xsl:with-param name="values" select="$values"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$values" mode="json-value"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="*|@*" mode="json-array-item"> + <xsl:apply-templates select="." mode="json-value"/> + <xsl:if test="position() != last()"> + <xsl:text>, </xsl:text> + </xsl:if> + </xsl:template> + + <!-- handle json-value --> + + <xsl:template match="text()[. = 'true'] | @*[. = 'true']" mode="json-value"> + <xsl:text>true</xsl:text> + </xsl:template> + + <xsl:template match="text()[. = 'false'] | @*[. = 'false']" + mode="json-value"> + <xsl:text>false</xsl:text> + </xsl:template> + + <xsl:template match="text()[string(number(.)) != 'NaN'] | @*[string(number(.)) != 'NaN']" + mode="json-value"> + <xsl:value-of select="number(.)" /> + </xsl:template> + + <xsl:template match="text()|@*" mode="json-value"> + <xsl:call-template name="write-string"> + <xsl:with-param name="text" select="."/> + </xsl:call-template> + </xsl:template> + + <xsl:template match="*[boolean(* | @*) or not(text())]" mode="json-value"> + <xsl:call-template name="write-object"/> + </xsl:template> + + <xsl:template match="*[@xsi:nil = 'true']" mode="json-value"> + <xsl:text>null</xsl:text> + </xsl:template> + + <!-- template traits --> + + <xsl:template name="write-value"> + <xsl:param name="value" select="."/> + <xsl:apply-templates select="$value" mode="json-value"/> + </xsl:template> + + <xsl:template name="write-member"> + <xsl:param name="name"/> + <xsl:param name="value"/> + <xsl:call-template name="write-string"> + <xsl:with-param name="text" select="$name"/> + </xsl:call-template> + <xsl:text> : </xsl:text> + <xsl:apply-templates select="$value" mode="json-value"/> + </xsl:template> + + <xsl:template name="write-member-string"> + <xsl:param name="name"/> + <xsl:param name="value"/> + <xsl:call-template name="write-string"> + <xsl:with-param name="text" select="$name"/> + </xsl:call-template> + <xsl:text> : </xsl:text> + <xsl:call-template name="write-string"> + <xsl:with-param name="text" select="$value"/> + </xsl:call-template> + </xsl:template> + + <xsl:template name="write-member-array"> + <xsl:param name="name"/> + <xsl:param name="values"/> + <xsl:call-template name="write-string"> + <xsl:with-param name="text" select="$name"/> + </xsl:call-template> + <xsl:text> : </xsl:text> + <xsl:call-template name="write-array"> + <xsl:with-param name="values" select="$values"/> + </xsl:call-template> + </xsl:template> + + <xsl:template name="write-separator"> + <xsl:text>, </xsl:text> + </xsl:template> + + <!-- specialized template traits --> + + <xsl:template name="write-string"> + <xsl:param name="text"/> + <xsl:text>"</xsl:text> + <xsl:call-template name="escape-bs-string"> + <xsl:with-param name="s" select="$text"/> + </xsl:call-template> + <xsl:text>"</xsl:text> + </xsl:template> + + <xsl:template name="write-object"> + <xsl:param name="value" select="."/> + <xsl:text>{ </xsl:text> + <xsl:apply-templates select="$value" mode="json-object"/> + <xsl:text> }</xsl:text> + </xsl:template> + + <xsl:template name="write-array"> + <xsl:param name="values"/> + + <xsl:text>[ </xsl:text> + <xsl:apply-templates select="$values" mode="json-array-item"/> + <xsl:text> ]</xsl:text> + </xsl:template> + + <xsl:template name="write-members"> + <xsl:param name="members" select="*"/> + + <xsl:for-each select="$members"> + <xsl:variable name="current" select="."/> + <xsl:variable name="values" select="$members[local-name(.) = local-name($current)]"/> + <xsl:if test="generate-id($current) = generate-id($values)"> + <xsl:if test="position()>1"> + <xsl:call-template name="write-separator"/> + </xsl:if> + <xsl:apply-templates select="$current" mode="json-member"> + <xsl:with-param name="values" select="$values"/> + </xsl:apply-templates> + </xsl:if> + </xsl:for-each> + </xsl:template> + + <!-- escape string --> + <!-- + Copyright (c) 2006,2008 Doeke Zanstra + All rights reserved. + https://github.com/doekman/xml2json-xslt/blob/master/xml2json.xslt + --> + <!-- Escape the backslash (\) before everything else. --> + <xsl:template name="escape-bs-string"> + <xsl:param name="s"/> + <xsl:choose> + <xsl:when test="contains($s,'\')"> + <xsl:call-template name="escape-quot-string"> + <xsl:with-param name="s" select="concat(substring-before($s,'\'),'\\')"/> + </xsl:call-template> + <xsl:call-template name="escape-bs-string"> + <xsl:with-param name="s" select="substring-after($s,'\')"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="escape-quot-string"> + <xsl:with-param name="s" select="$s"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- Escape the double quote ("). --> + <xsl:template name="escape-quot-string"> + <xsl:param name="s"/> + <xsl:choose> + <xsl:when test="contains($s,'"')"> + <xsl:call-template name="encode-string"> + <xsl:with-param name="s" select="concat(substring-before($s,'"'),'\"')"/> + </xsl:call-template> + <xsl:call-template name="escape-quot-string"> + <xsl:with-param name="s" select="substring-after($s,'"')"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="encode-string"> + <xsl:with-param name="s" select="$s"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- Replace tab, line feed and/or carriage return by its matching escape code. Can't escape backslash + or double quote here, because they don't replace characters (� becomes \t), but they prefix + characters (\ becomes \\). Besides, backslash should be seperate anyway, because it should be + processed first. This function can't do that. --> + <xsl:template name="encode-string"> + <xsl:param name="s"/> + <xsl:choose> + <!-- tab --> + <xsl:when test="contains($s,'	')"> + <xsl:call-template name="encode-string"> + <xsl:with-param name="s" select="concat(substring-before($s,'	'),'\t',substring-after($s,'	'))"/> + </xsl:call-template> + </xsl:when> + <!-- line feed --> + <xsl:when test="contains($s,'
')"> + <xsl:call-template name="encode-string"> + <xsl:with-param name="s" select="concat(substring-before($s,'
'),'\n',substring-after($s,'
'))"/> + </xsl:call-template> + </xsl:when> + <!-- carriage return --> + <xsl:when test="contains($s,'
')"> + <xsl:call-template name="encode-string"> + <xsl:with-param name="s" select="concat(substring-before($s,'
'),'\r',substring-after($s,'
'))"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise><xsl:value-of select="$s"/></xsl:otherwise> + </xsl:choose> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Xml/readme.txt Mon Apr 16 18:43:49 2018 +0300 @@ -0,0 +1,4 @@ +XML to JSON transform is taken from different project https://hg.implab.org/pub/ModelGenerator/ +run: + wget https://hg.implab.org/pub/ModelGenerator/raw-file/tip/xslt/json.xsl +to update to the latest version \ No newline at end of file