-Added StringEscapeHandling with options to escape HTML and non-ASCII characters

-Added feature to deserialize ISet<T> properties as HashSet<T>
-Fixed ReferenceLoopHandling not being used when serializing ISerializable and dynamic values
This commit is contained in:
James Newton-King
2012-11-18 14:36:00 +13:00
parent f7018719d4
commit e3be3e43db
22 changed files with 515 additions and 38 deletions
@@ -841,6 +841,69 @@ _____'propertyName': NaN
Assert.AreEqual(@"""2000-01-01T01:01:01Z""", sw.ToString());
}
[Test]
public void HtmlStringEscapeHandling()
{
StringWriter sw = new StringWriter();
JsonTextWriter writer = new JsonTextWriter(sw)
{
StringEscapeHandling = StringEscapeHandling.EscapeHtml
};
string script = @"<script type=""text/javascript"">alert('hi');</script>";
writer.WriteValue(script);
string json = sw.ToString();
Assert.AreEqual(@"""\u003cscript type=\u0022text/javascript\u0022\u003ealert(\u0027hi\u0027);\u003c/script\u003e""", json);
JsonTextReader reader = new JsonTextReader(new StringReader(json));
Assert.AreEqual(script, reader.ReadAsString());
//Console.WriteLine(HttpUtility.HtmlEncode(script));
//System.Web.Script.Serialization.JavaScriptSerializer s = new System.Web.Script.Serialization.JavaScriptSerializer();
//Console.WriteLine(s.Serialize(new { html = script }));
}
[Test]
public void NonAsciiStringEscapeHandling()
{
StringWriter sw = new StringWriter();
JsonTextWriter writer = new JsonTextWriter(sw)
{
StringEscapeHandling = StringEscapeHandling.EscapeNonAscii
};
string unicode = "\u5f20";
writer.WriteValue(unicode);
string json = sw.ToString();
Assert.AreEqual(8, json.Length);
Assert.AreEqual(@"""\u5f20""", json);
JsonTextReader reader = new JsonTextReader(new StringReader(json));
Assert.AreEqual(unicode, reader.ReadAsString());
sw = new StringWriter();
writer = new JsonTextWriter(sw)
{
StringEscapeHandling = StringEscapeHandling.Default
};
writer.WriteValue(unicode);
json = sw.ToString();
Assert.AreEqual(3, json.Length);
Assert.AreEqual("\"\u5f20\"", json);
}
[Test]
public void WriteEndOnProperty()
{
@@ -291,6 +291,48 @@ namespace Newtonsoft.Json.Tests
Console.WriteLine("XML: " + watch.Elapsed.TotalSeconds);
}
[Test]
public void SerializeString()
{
string text = @"The general form of an HTML element is therefore: <tag attribute1=""value1"" attribute2=""value2"">content</tag>.
Some HTML elements are defined as empty elements and take the form <tag attribute1=""value1"" attribute2=""value2"" >.
Empty elements may enclose no content, for instance, the BR tag or the inline IMG tag.
The name of an HTML element is the name used in the tags.
Note that the end tag's name is preceded by a slash character, ""/"", and that in empty elements the end tag is neither required nor allowed.
If attributes are not mentioned, default values are used in each case.
The general form of an HTML element is therefore: <tag attribute1=""value1"" attribute2=""value2"">content</tag>.
Some HTML elements are defined as empty elements and take the form <tag attribute1=""value1"" attribute2=""value2"" >.
Empty elements may enclose no content, for instance, the BR tag or the inline IMG tag.
The name of an HTML element is the name used in the tags.
Note that the end tag's name is preceded by a slash character, ""/"", and that in empty elements the end tag is neither required nor allowed.
If attributes are not mentioned, default values are used in each case.
The general form of an HTML element is therefore: <tag attribute1=""value1"" attribute2=""value2"">content</tag>.
Some HTML elements are defined as empty elements and take the form <tag attribute1=""value1"" attribute2=""value2"" >.
Empty elements may enclose no content, for instance, the BR tag or the inline IMG tag.
The name of an HTML element is the name used in the tags.
Note that the end tag's name is preceded by a slash character, ""/"", and that in empty elements the end tag is neither required nor allowed.
If attributes are not mentioned, default values are used in each case.
";
int interations = 1000;
TimeOperation(() =>
{
string json = null;
for (int i = 0; i < interations; i++)
{
using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(text) ?? 16))
{
JavaScriptUtils.WriteEscapedJavaScriptString(w, text, '"', true, JavaScriptUtils.DoubleQuoteCharEscapeFlags, StringEscapeHandling.Default);
}
}
return "";
}, "New");
}
private void SerializeSize(object value)
{
// this is extremely slow with 5000 interations
@@ -76,5 +76,5 @@ using System.Security;
// by using the '*' as shown below:
[assembly: AssemblyVersion("4.5.0.0")]
#if !PocketPC
[assembly: AssemblyFileVersion("4.5.10.15512")]
[assembly: AssemblyFileVersion("4.5.10.15518")]
#endif
@@ -7154,6 +7154,68 @@ Parameter name: value",
Assert.IsNull(c2.MyNullableDecimal);
}
#endif
#if !(PORTABLE || NET20 || NET35 || WINDOWS_PHONE)
[Test]
public void HashSetInterface()
{
ISet<string> s1 = new HashSet<string>(new[] {"1", "two", "III"});
string json = JsonConvert.SerializeObject(s1);
ISet<string> s2 = JsonConvert.DeserializeObject<ISet<string>>(json);
Assert.AreEqual(s1.Count, s2.Count);
foreach (string s in s1)
{
Assert.IsTrue(s2.Contains(s));
}
}
#endif
public class NewEmployee : Employee
{
public int Age { get; set; }
public bool ShouldSerializeName()
{
return false;
}
}
[Test]
public void ShouldSerializeInheritedClassTest()
{
NewEmployee joe = new NewEmployee();
joe.Name = "Joe Employee";
joe.Age = 100;
Employee mike = new Employee();
mike.Name = "Mike Manager";
mike.Manager = mike;
joe.Manager = mike;
//StringWriter sw = new StringWriter();
//XmlSerializer x = new XmlSerializer(typeof(NewEmployee));
//x.Serialize(sw, joe);
//Console.WriteLine(sw);
//JavaScriptSerializer s = new JavaScriptSerializer();
//Console.WriteLine(s.Serialize(new {html = @"<script>hi</script>; & ! ^ * ( ) ! @ # $ % ^ ' "" - , . / ; : [ { } ] ; ' - _ = + ? ` ~ \ |"}));
string json = JsonConvert.SerializeObject(joe, Formatting.Indented);
Assert.AreEqual(@"{
""Age"": 100,
""Name"": ""Joe Employee"",
""Manager"": {
""Name"": ""Mike Manager""
}
}", json);
}
}
#if !(SILVERLIGHT || NETFX_CORE || PORTABLE)
@@ -23,6 +23,7 @@
// OTHER DEALINGS IN THE SOFTWARE.
#endregion
using System;
using System.Collections.Generic;
#if !NETFX_CORE
using NUnit.Framework;
@@ -31,6 +32,11 @@ using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute;
using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute;
#endif
#if !(NET20 || NET35 || WINDOWS_PHONE)
using System.Dynamic;
#endif
using System.Runtime.Serialization;
using Newtonsoft.Json.Tests.Linq;
namespace Newtonsoft.Json.Tests.Serialization
{
@@ -142,6 +148,126 @@ namespace Newtonsoft.Json.Tests.Serialization
]
}", json);
}
#if !(PORTABLE || SILVERLIGHT)
public class MainClass : ISerializable
{
public ChildClass Child { get; set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Child", Child);
}
}
public class ChildClass : ISerializable
{
public string Name { get; set; }
public MainClass Parent { get; set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Parent", Parent);
info.AddValue("Name", Name);
}
}
[Test]
public void ErrorISerializableCyclicReferenceLoop()
{
var main = new MainClass();
var child = new ChildClass();
child.Name = "Child1";
child.Parent = main; // Obvious Circular Reference
main.Child = child;
var settings =
new JsonSerializerSettings();
ExceptionAssert.Throws<JsonSerializationException>(
"Self referencing loop detected with type 'Newtonsoft.Json.Tests.Serialization.ReferenceLoopHandlingTests+MainClass'. Path 'Child'.",
() => JsonConvert.SerializeObject(main, settings));
}
[Test]
public void IgnoreISerializableCyclicReferenceLoop()
{
var main = new MainClass();
var child = new ChildClass();
child.Name = "Child1";
child.Parent = main; // Obvious Circular Reference
main.Child = child;
var settings =
new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var c = JsonConvert.SerializeObject(main, settings);
Assert.AreEqual(@"{""Child"":{""Name"":""Child1""}}", c);
}
#endif
#if !(PORTABLE || NET20 || NET35 || WINDOWS_PHONE)
public class DictionaryDynamicObject : DynamicObject
{
public IDictionary<string, object> Values { get; private set; }
public DictionaryDynamicObject()
{
Values = new Dictionary<string, object>();
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Values[binder.Name] = value;
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return Values.TryGetValue(binder.Name, out result);
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return Values.Keys;
}
}
[Test]
public void ErrorDynamicCyclicReferenceLoop()
{
dynamic parent = new DictionaryDynamicObject();
dynamic child = new DictionaryDynamicObject();
parent.child = child;
child.parent = parent;
var settings = new JsonSerializerSettings();
ExceptionAssert.Throws<JsonSerializationException>(
"Self referencing loop detected with type 'Newtonsoft.Json.Tests.Serialization.ReferenceLoopHandlingTests+DictionaryDynamicObject'. Path 'child'.",
() => JsonConvert.SerializeObject(parent, settings));
}
[Test]
public void IgnoreDynamicCyclicReferenceLoop()
{
dynamic parent = new DictionaryDynamicObject();
dynamic child = new DictionaryDynamicObject();
parent.child = child;
parent.name = "parent";
child.parent = parent;
child.name = "child";
var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var c = JsonConvert.SerializeObject(parent, settings);
Assert.AreEqual(@"{""child"":{""name"":""child""},""name"":""parent""}", c);
}
#endif
}
public class PropertyItemReferenceLoopHandling
+6 -3
View File
@@ -556,11 +556,14 @@ namespace Newtonsoft.Json
/// Converts the <see cref="String"/> to its JSON string representation.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="delimter">The string delimiter character.</param>
/// <param name="delimiter">The string delimiter character.</param>
/// <returns>A JSON string representation of the <see cref="String"/>.</returns>
public static string ToString(string value, char delimter)
public static string ToString(string value, char delimiter)
{
return JavaScriptUtils.ToEscapedJavaScriptString(value, delimter, true);
if (delimiter != '"' && delimiter != '\'')
throw new ArgumentException("Delimiter must be a single or double quote.", "delimiter");
return JavaScriptUtils.ToEscapedJavaScriptString(value, delimiter, true);
}
/// <summary>
+13 -2
View File
@@ -206,11 +206,22 @@ namespace Newtonsoft.Json
{
InternalWritePropertyName(name);
JavaScriptUtils.WriteEscapedJavaScriptString(_writer, name, _quoteChar, _quoteName);
JavaScriptUtils.WriteEscapedJavaScriptString(_writer, name, _quoteChar, _quoteName, GetCharEscapeFlags(), StringEscapeHandling);
_writer.Write(':');
}
private bool[] GetCharEscapeFlags()
{
if (StringEscapeHandling == StringEscapeHandling.EscapeHtml)
return JavaScriptUtils.HtmlCharEscapeFlags;
if (_quoteChar == '"')
return JavaScriptUtils.DoubleQuoteCharEscapeFlags;
return JavaScriptUtils.SingleQuoteCharEscapeFlags;
}
/// <summary>
/// Writes indent characters.
/// </summary>
@@ -294,7 +305,7 @@ namespace Newtonsoft.Json
if (value == null)
WriteValueInternal(JsonConvert.Null, JsonToken.Null);
else
JavaScriptUtils.WriteEscapedJavaScriptString(_writer, value, _quoteChar, true);
JavaScriptUtils.WriteEscapedJavaScriptString(_writer, value, _quoteChar, true, GetCharEscapeFlags(), StringEscapeHandling);
}
/// <summary>
+11 -1
View File
@@ -209,6 +209,7 @@ namespace Newtonsoft.Json
private DateFormatHandling _dateFormatHandling;
private DateTimeZoneHandling _dateTimeZoneHandling;
private StringEscapeHandling _stringEscapeHandling;
/// <summary>
/// Indicates how JSON text output is formatted.
@@ -229,7 +230,7 @@ namespace Newtonsoft.Json
}
/// <summary>
/// Get or set how <see cref="DateTime"/> time zones are handling when writing JSON.
/// Get or set how <see cref="DateTime"/> time zones are handling when writing JSON text.
/// </summary>
public DateTimeZoneHandling DateTimeZoneHandling
{
@@ -237,6 +238,15 @@ namespace Newtonsoft.Json
set { _dateTimeZoneHandling = value; }
}
/// <summary>
/// Get or set how strings are escaped when writing JSON text.
/// </summary>
public StringEscapeHandling StringEscapeHandling
{
get { return _stringEscapeHandling; }
set { _stringEscapeHandling = value; }
}
/// <summary>
/// Creates an instance of the <c>JsonWriter</c> class.
/// </summary>
@@ -109,6 +109,7 @@
<Compile Include="Serialization\JsonDynamicContract.cs" />
<Compile Include="Serialization\MemoryTraceWriter.cs" />
<Compile Include="StreamingContext.cs" />
<Compile Include="StringEscapeHandling.cs" />
<Compile Include="Utilities\LinqBridge.cs" />
<Compile Include="Linq\JPath.cs" />
<Compile Include="Linq\JPropertyDescriptor.cs" />
@@ -121,6 +121,7 @@
<Compile Include="Serialization\JsonContainerContract.cs" />
<Compile Include="Serialization\MemoryTraceWriter.cs" />
<Compile Include="StreamingContext.cs" />
<Compile Include="StringEscapeHandling.cs" />
<Compile Include="TraceLevel.cs" />
<Compile Include="Utilities\DynamicProxy.cs" />
<Compile Include="Linq\JPath.cs" />
@@ -175,6 +175,7 @@
<Compile Include="Serialization\OnErrorAttribute.cs" />
<Compile Include="Serialization\ReflectionValueProvider.cs" />
<Compile Include="StreamingContext.cs" />
<Compile Include="StringEscapeHandling.cs" />
<Compile Include="TraceLevel.cs" />
<Compile Include="TypeNameHandling.cs" />
<Compile Include="Utilities\Base64Encoder.cs" />
@@ -197,6 +197,7 @@
<Compile Include="Serialization\OnErrorAttribute.cs" />
<Compile Include="Serialization\ReflectionValueProvider.cs" />
<Compile Include="StreamingContext.cs" />
<Compile Include="StringEscapeHandling.cs" />
<Compile Include="TraceLevel.cs" />
<Compile Include="TypeNameHandling.cs" />
<Compile Include="Utilities\Base64Encoder.cs" />
@@ -170,6 +170,7 @@
<Compile Include="Serialization\OnErrorAttribute.cs" />
<Compile Include="Serialization\ReflectionValueProvider.cs" />
<Compile Include="StreamingContext.cs" />
<Compile Include="StringEscapeHandling.cs" />
<Compile Include="TraceLevel.cs" />
<Compile Include="TypeNameHandling.cs" />
<Compile Include="Utilities\Base64Encoder.cs" />
@@ -117,6 +117,7 @@
<Compile Include="Serialization\ITraceWriter.cs" />
<Compile Include="Serialization\JsonContainerContract.cs" />
<Compile Include="Serialization\MemoryTraceWriter.cs" />
<Compile Include="StringEscapeHandling.cs" />
<Compile Include="Utilities\DynamicProxy.cs" />
<Compile Include="Linq\JPath.cs" />
<Compile Include="Linq\JRaw.cs" />
@@ -90,7 +90,7 @@ using System.Security;
// by using the '*' as shown below:
[assembly: AssemblyVersion("4.5.0.0")]
#if !PocketPC
[assembly: AssemblyFileVersion("4.5.10.15512")]
[assembly: AssemblyFileVersion("4.5.10.15518")]
#endif
[assembly: CLSCompliant(true)]
@@ -5,8 +5,19 @@ using DiagnosticsTrace = System.Diagnostics.Trace;
namespace Newtonsoft.Json.Serialization
{
/// <summary>
/// Represents a trace writer that writes to the application's <see cref="TraceListener"/> instances.
/// </summary>
public class DiagnosticsTraceWriter : ITraceWriter
{
/// <summary>
/// Gets the <see cref="TraceLevel"/> that will be used to filter the trace messages passed to the writer.
/// For example a filter level of <code>Info</code> will exclude <code>Verbose</code> messages and include <code>Info</code>,
/// <code>Warning</code> and <code>Error</code> messages.
/// </summary>
/// <value>
/// The <see cref="TraceLevel"/> that will be used to filter the trace messages passed to the writer.
/// </value>
public TraceLevel LevelFilter { get; set; }
private TraceEventType GetTraceEventType(TraceLevel level)
@@ -26,6 +37,12 @@ namespace Newtonsoft.Json.Serialization
}
}
/// <summary>
/// Writes the specified trace level, message and optional exception.
/// </summary>
/// <param name="level">The <see cref="TraceLevel"/> at which to write this trace.</param>
/// <param name="message">The trace message.</param>
/// <param name="ex">The trace exception. This parameter is optional.</param>
public void Trace(TraceLevel level, string message, Exception ex)
{
if (level == TraceLevel.Off)
@@ -85,9 +85,11 @@ namespace Newtonsoft.Json.Serialization
_isCollectionItemTypeNullableType = ReflectionUtils.IsNullableType(CollectionItemType);
if (IsTypeGenericCollectionInterface(UnderlyingType))
{
CreatedType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType);
}
#if !(PORTABLE || NET20 || NET35 || WINDOWS_PHONE)
else if (IsTypeGenericSetnterface(UnderlyingType))
CreatedType = ReflectionUtils.MakeGenericType(typeof(HashSet<>), CollectionItemType);
#endif
IsMultidimensionalArray = (UnderlyingType.IsArray && UnderlyingType.GetArrayRank() > 1);
}
@@ -152,5 +154,17 @@ namespace Newtonsoft.Json.Serialization
|| genericDefinition == typeof(ICollection<>)
|| genericDefinition == typeof(IEnumerable<>));
}
#if !(PORTABLE || NET20 || NET35 || WINDOWS_PHONE)
private bool IsTypeGenericSetnterface(Type type)
{
if (!type.IsGenericType())
return false;
Type genericDefinition = type.GetGenericTypeDefinition();
return (genericDefinition == typeof(ISet<>));
}
#endif
}
}
@@ -31,7 +31,7 @@ namespace Newtonsoft.Json.Serialization
/// <summary>
/// Contract details for a <see cref="Type"/> used by the <see cref="JsonSerializer"/>.
/// </summary>
public class JsonISerializableContract : JsonContract
public class JsonISerializableContract : JsonContainerContract
{
/// <summary>
/// Gets or sets the ISerializable object constructor.
@@ -682,8 +682,13 @@ To fix this error either change the environment to be fully trusted, change the
foreach (SerializationEntry serializationEntry in serializationInfo)
{
writer.WritePropertyName(serializationEntry.Name);
SerializeValue(writer, serializationEntry.Value, GetContractSafe(serializationEntry.Value), null, null, member);
JsonContract valueContract = GetContractSafe(serializationEntry.Value);
if (CheckForCircularReference(writer, serializationEntry.Value, null, valueContract, contract, member))
{
writer.WritePropertyName(serializationEntry.Name);
SerializeValue(writer, serializationEntry.Value, valueContract, null, contract, member);
}
}
writer.WriteEndObject();
@@ -736,12 +741,17 @@ To fix this error either change the environment to be fully trusted, change the
{
try
{
string resolvedPropertyName = (contract.PropertyNameResolver != null)
? contract.PropertyNameResolver(memberName)
: memberName;
JsonContract valueContract = GetContractSafe(memberValue);
writer.WritePropertyName(resolvedPropertyName);
SerializeValue(writer, memberValue, GetContractSafe(memberValue), null, null, member);
if (CheckForCircularReference(writer, memberValue, null, valueContract, contract, member))
{
string resolvedPropertyName = (contract.PropertyNameResolver != null)
? contract.PropertyNameResolver(memberName)
: memberName;
writer.WritePropertyName(resolvedPropertyName);
SerializeValue(writer, memberValue, valueContract, null, contract, member);
}
}
catch (Exception ex)
{
@@ -5,18 +5,39 @@ using System.Text;
namespace Newtonsoft.Json.Serialization
{
/// <summary>
/// Represents a trace writer that writes to memory. When the trace message limit is
/// reached then old trace messages will be removed as new messages are added.
/// </summary>
public class MemoryTraceWriter : ITraceWriter
{
private readonly Queue<string> _traceMessages;
/// <summary>
/// Gets the <see cref="TraceLevel"/> that will be used to filter the trace messages passed to the writer.
/// For example a filter level of <code>Info</code> will exclude <code>Verbose</code> messages and include <code>Info</code>,
/// <code>Warning</code> and <code>Error</code> messages.
/// </summary>
/// <value>
/// The <see cref="TraceLevel"/> that will be used to filter the trace messages passed to the writer.
/// </value>
public TraceLevel LevelFilter { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MemoryTraceWriter"/> class.
/// </summary>
public MemoryTraceWriter()
{
LevelFilter = TraceLevel.Verbose;
_traceMessages = new Queue<string>();
}
/// <summary>
/// Writes the specified trace level, message and optional exception.
/// </summary>
/// <param name="level">The <see cref="TraceLevel"/> at which to write this trace.</param>
/// <param name="message">The trace message.</param>
/// <param name="ex">The trace exception. This parameter is optional.</param>
public void Trace(TraceLevel level, string message, Exception ex)
{
string traceMessage = DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff") + " " + level.ToString("g") + " " + message;
@@ -27,11 +48,21 @@ namespace Newtonsoft.Json.Serialization
_traceMessages.Enqueue(traceMessage);
}
/// <summary>
/// Returns an enumeration of the most recent trace messages.
/// </summary>
/// <returns>An enumeration of the most recent trace messages.</returns>
public IEnumerable<string> GetTraceMessages()
{
return _traceMessages;
}
/// <summary>
/// Returns a <see cref="String"/> of the most recent trace messages.
/// </summary>
/// <returns>
/// A <see cref="String"/> of the most recent trace messages.
/// </returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
@@ -0,0 +1,46 @@
#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion
namespace Newtonsoft.Json
{
/// <summary>
/// Specifies how strings are escaped when writing JSON text.
/// </summary>
public enum StringEscapeHandling
{
/// <summary>
/// Only control characters (e.g. newline) are escaped.
/// </summary>
Default,
/// <summary>
/// All non-ASCII and control characters (e.g. newline) are escaped.
/// </summary>
EscapeNonAscii,
/// <summary>
/// HTML (&lt;, &gt;, &amp;, &apos;, &quot;) and control characters (e.g. newline) are escaped.
/// </summary>
EscapeHtml
}
}
@@ -30,14 +30,48 @@ using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
#if NET20
using Newtonsoft.Json.Utilities.LinqBridge;
#else
using System.Linq;
#endif
namespace Newtonsoft.Json.Utilities
{
internal static class JavaScriptUtils
{
internal static readonly bool[] SingleQuoteCharEscapeFlags = new bool[128];
internal static readonly bool[] DoubleQuoteCharEscapeFlags = new bool[128];
internal static readonly bool[] HtmlCharEscapeFlags = new bool[128];
static JavaScriptUtils()
{
IList<char> escapeChars = new List<char>
{
'\n', '\r', '\t', '\\', '\f', '\b',
};
for (int i = 0; i < ' '; i++)
{
escapeChars.Add((char)i);
}
foreach (var escapeChar in escapeChars.Union(new[] { '\'' }))
{
SingleQuoteCharEscapeFlags[escapeChar] = true;
}
foreach (var escapeChar in escapeChars.Union(new[] { '"' }))
{
DoubleQuoteCharEscapeFlags[escapeChar] = true;
}
foreach (var escapeChar in escapeChars.Union(new[] { '"', '\'', '<', '>', '&' }))
{
HtmlCharEscapeFlags[escapeChar] = true;
}
}
private const string EscapedUnicodeText = "!";
public static void WriteEscapedJavaScriptString(TextWriter writer, string s, char delimiter, bool appendDelimiters)
public static void WriteEscapedJavaScriptString(TextWriter writer, string s, char delimiter, bool appendDelimiters, bool[] charEscapeFlags, StringEscapeHandling stringEscapeHandling)
{
// leading delimiter
if (appendDelimiters)
@@ -53,8 +87,7 @@ namespace Newtonsoft.Json.Utilities
{
var c = s[i];
// don't escape standard text/numbers except '\' and the text delimiter
if (c >= ' ' && c < 128 && c != '\\' && c != delimiter)
if (c < charEscapeFlags.Length && !charEscapeFlags[c])
continue;
string escapedValue;
@@ -88,24 +121,27 @@ namespace Newtonsoft.Json.Utilities
case '\u2029': // Paragraph Separator
escapedValue = @"\u2029";
break;
case '\'':
// this charater is being used as the delimiter
escapedValue = @"\'";
break;
case '"':
// this charater is being used as the delimiter
escapedValue = "\\\"";
break;
default:
if (c <= '\u001f')
if (c < charEscapeFlags.Length || stringEscapeHandling == StringEscapeHandling.EscapeNonAscii)
{
if (unicodeBuffer == null)
unicodeBuffer = new char[6];
if (c == '\'' && stringEscapeHandling != StringEscapeHandling.EscapeHtml)
{
escapedValue = @"\'";
}
else if (c == '"' && stringEscapeHandling != StringEscapeHandling.EscapeHtml)
{
escapedValue = @"\""";
}
else
{
if (unicodeBuffer == null)
unicodeBuffer = new char[6];
StringUtils.ToCharAsUnicode(c, unicodeBuffer);
StringUtils.ToCharAsUnicode(c, unicodeBuffer);
// slightly hacky but it saves multiple conditions in if test
escapedValue = EscapedUnicodeText;
// slightly hacky but it saves multiple conditions in if test
escapedValue = EscapedUnicodeText;
}
}
else
{
@@ -162,7 +198,7 @@ namespace Newtonsoft.Json.Utilities
{
using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(value) ?? 16))
{
WriteEscapedJavaScriptString(w, value, delimiter, appendDelimiters);
WriteEscapedJavaScriptString(w, value, delimiter, appendDelimiters, (delimiter == '"') ? DoubleQuoteCharEscapeFlags : SingleQuoteCharEscapeFlags, StringEscapeHandling.Default);
return w.ToString();
}
}