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>&quot;</xsl:text>
+		<xsl:call-template name="escape-bs-string">
+			<xsl:with-param name="s" select="$text"/>
+		</xsl:call-template>
+		<xsl:text>&quot;</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,'&quot;')">
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'&quot;'),'\&quot;')"/>
+        </xsl:call-template>
+        <xsl:call-template name="escape-quot-string">
+          <xsl:with-param name="s" select="substring-after($s,'&quot;')"/>
+        </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 (&#x0; 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,'&#x9;')">
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'&#x9;'),'\t',substring-after($s,'&#x9;'))"/>
+        </xsl:call-template>
+      </xsl:when>
+      <!-- line feed -->
+      <xsl:when test="contains($s,'&#xA;')">
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'&#xA;'),'\n',substring-after($s,'&#xA;'))"/>
+        </xsl:call-template>
+      </xsl:when>
+      <!-- carriage return -->
+      <xsl:when test="contains($s,'&#xD;')">
+        <xsl:call-template name="encode-string">
+          <xsl:with-param name="s" select="concat(substring-before($s,'&#xD;'),'\r',substring-after($s,'&#xD;'))"/>
+        </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