Escape Quote in C# for javascript consumption - c#

I have a ASP.Net web handler that returns results of a query in JSON format
public static String dt2JSON(DataTable dt)
{
String s = "{\"rows\":[";
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
s += "{";
for (int i = 0; i < dr.Table.Columns.Count; i++)
{
s += "\"" + dr.Table.Columns[i].ToString() + "\":\"" + dr[i].ToString() + "\",";
}
s = s.Remove(s.Length - 1, 1);
s += "},";
}
s = s.Remove(s.Length - 1, 1);
}
s += "]}";
return s;
}
The problem is that sometimes the data returned has quotes in it and I would need to javascript-escape these so that it can be properly created into a js object. I need a way to find quotes in my data (quotes aren't there every time) and place a "/" character in front of them.
Example response text (wrong):
{"rows":[{"id":"ABC123","length":"5""},
{"id":"DEF456","length":"1.35""},
{"id":"HIJ789","length":"36.25""}]}
I would need to escape the " so my response should be:
{"rows":[{"id":"ABC123","length":"5\""},
{"id":"DEF456","length":"1.35\""},
{"id":"HIJ789","length":"36.25\""}]}
Also, I'm pretty new to C# (coding in general really) so if something else in my code looks silly let me know.

For .net 4.0 + there is standard
HttpUtility.JavaScriptStringEncode
For earlier west wind solution described by Lone Coder is quite nice

Here is an efficient and robust method that I found at http://www.west-wind.com/weblog/posts/114530.aspx
/// <summary>
/// Encodes a string to be represented as a string literal. The format
/// is essentially a JSON string.
///
/// The string returned includes outer quotes
/// Example Output: "Hello \"Rick\"!\r\nRock on"
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string EncodeJsString(string s)
{
StringBuilder sb = new StringBuilder();
sb.Append("\"");
foreach (char c in s)
{
switch (c)
{
case '\"':
sb.Append("\\\"");
break;
case '\\':
sb.Append("\\\\");
break;
case '\b':
sb.Append("\\b");
break;
case '\f':
sb.Append("\\f");
break;
case '\n':
sb.Append("\\n");
break;
case '\r':
sb.Append("\\r");
break;
case '\t':
sb.Append("\\t");
break;
default:
int i = (int)c;
if (i < 32 || i > 127)
{
sb.AppendFormat("\\u{0:X04}", i);
}
else
{
sb.Append(c);
}
break;
}
}
sb.Append("\"");
return sb.ToString();
}

I think you should rather look at the JavaScriptSerializer class. It's a lot more stable, and will correctly handle any kind of data or escape characters etc. Also, your code will look a lot cleaner.
In your case your class can look like this:
public static String dt2JSON(DataTable dt) {
var rows = new List<Object>();
foreach(DataRow row in dt.Rows)
{
var rowData = new Dictionary<string, object>();
foreach(DataColumn col in dt.Columns)
rowData[col.ColumnName] = row[col];
rows.Add(rowData);
}
var js = new JavaScriptSerializer();
return js.Serialize(new { rows = rows });
}
This method will return a correctly serialized json string... For example, sth like this:
{"rows":[{"id":1,"name":"hello"},{"id":2,"name":"bye"}]}
Have fun! :)

To correctly escape a string literal for Javascript, you first escape all backslash characters, then you escape the quotation marks (or apostrophes if you use them as string delimiters).
So, what you need is:
value.Replace("\\","\\\\").Replace("\"","\\\"")
What else jumps out to me is that you are using string concatenation in a loop. This is bad, as it scales very poorly. The += operator does not add characters at the end of the existing string (as strings are immutable and can never be changed), instead it copies the string and the added characters to a new string. As you copy more and more data each time, eEvery additional row roughly doubles the execution time of the method. Use a StringBuilder to build the string instead.
Use the ColumnName property to get the name of a column rather than the ToString method. The ToString method returns the Expression property value if it's set, only if that is not set does it return the ColumnName property.
public static String dt2JSON(DataTable dt) {
StringBuilder s = new StringBuilder("{\"rows\":[");
bool firstLine = true;
foreach (DataRow dr in dt.Rows) {
if (firstLine) {
firstLine = false;
} else {
s.Append(',');
}
s.Append('{');
for (int i = 0; i < dr.Table.Columns.Count; i++) {
if (i > 0) {
s.Append(',');
}
string name = dt.Columns[i].ColumnName;
string value = dr[i].ToString();
s.Append('"')
.Append(name.Replace("\\","\\\\").Replace("\"","\\\""))
.Append("\":\"")
.Append(value.Replace("\\","\\\\").Replace("\"","\\\""))
.Append('"');
}
s.Append("}");
}
s.Append("]}");
return s.ToString();
}

string.Replace(<mystring>, #"\"", #"\\"");

Why don't you just do this:
string correctResponseText = wrongResponseText.Replace("\"", "\\\"");

Works when i need send string from C# to html tag.
<buton onlick="alert('<< here >>')" />
HttpUtility.HtmlEncode

Here is a rework of #Bryan Legend's answer with Linq:
public static string EncodeJavaScriptString(string s)
=> string.Concat(s.Select(c => {
switch (c)
{
case '\"': return "\\\"";
case '\\': return "\\\\";
case '\b': return "\\b";
case '\f': return "\\f";
case '\n': return "\\n";
case '\r': return "\\r";
case '\t': return "\\t";
default: return (c < 32 || c > 127) && !char.IsLetterOrDigit(c) ? $"\\u{(int)c:X04}" : c.ToString();
}}));
Try it Online!
changelog:
Remove double quoting wrapping since I do it in the js
Use expression body
Use a cleaner switch
Use Linq
Add a check for Letter to allow é

Well, for starters you do not need quotes around the keys.
{rows:[,]} is valid.
and you could dt.Table.Columns[i].ToString().Replace("\","")
But if you want to retain the double quotes, single quote works the same way double quotes do in JS
Otherwise you could do
String.Format("{name: \"{0}\"}",Columns[i].ToString().Replace("\",""))

Related

How to solve this challenge using a FIFO stack?

I'm using a portion of C# code from Sanjit Prasad to solve the challenge of processing backspaces in a given string of words. The new challenge is to process the left-arrow-key and right-arrow-key in combination with backspaces, reflecting a "corrector" for typos in writing.
The following string represents the problem and solution for the first challenge using a FIFO stack (credits to Sanjit Prasad):
string: thiss# is a txt##ext with some typos
expected result: this is a text with some typos
This is the code to generate the expected result:
static String finalAnswer(String S)
{
Stack<Char> q = new Stack<Char>();
for (int i = 0; i < S.Length; ++i)
{
if (S[i] != '#') q.Push(S[i]);
else if (q.Count!=0) q.Pop();
}
String ans = "";
while (q.Count!=0)
{
ans += q.Pop();
}
String answer = "";
for(int j = ans.Length - 1; j >= 0; j--)
{
answer += ans[j];
}
return answer;
}
That code works great, now, the challenge is to process the following string:
string: ths#is is an te\\\#///xt wit some\\\\\h///// tpos###ypos
expected result: this is a text with some typos
In the above string, the character "\" represents a left arrow key pressed, and "/" a right arrow key pressed.
Thank you so much for all your comments, this is my very first question in Stackoverflow, I would like to know an approach to solve the this challenge.
Here is a version that gets the job done, but will need some checks for edge cases and some more error checks.
static string GenerateUpdatedString(string strInput)
{
var stringStack = new Stack<char>();//holds the string as it is read from the input
var workingStack = new Stack<char>();//hold chars when going back to fix typos
char poppedChar;
foreach (var ch in strInput)
{
switch (ch)
{
case '\\':
{
PushAndPopCharacters(workingStack, stringStack);
break;
}
case '/':
{
PushAndPopCharacters(stringStack, workingStack);
break;
}
case '#':
{
stringStack.TryPop(out poppedChar);
break;
}
default:
stringStack.Push(ch);
break;
}
}
return new string(stringStack.Reverse().ToArray());
}
static void PushAndPopCharacters(Stack<char> stackToPush, Stack<char> stackToPop)
{
char poppedChar;
if (stackToPop.TryPop(out poppedChar))
{
stackToPush.Push(poppedChar);
}
}
Usage
var result = GenerateUpdatedString
(#"ths#is is an te\\\#///xt wit some\\\\\h///// tpos###ypos");

Make command line from `IEnumerable` of strings

Question: Is there a function in .NET (or PInvokable) that would prepare a command line (in the shape of one string); from a IEnumerable of strings.
That would be the equivalent of python's subproces.list2cmdline, but in .NET.
And it would be the reverse of Win32's CommandLineToArgvW.
As a sketch, it could probably be grossly approximated by:
static public string List2CmdLine(IEnumerable<string> args)
{
return string.Join(" ", args.Select((s) => "\"" + s + "\"")))
}
details:
python's subprocess.list2cmdline documentation states that what they do, goes by respecting the rules of the Windows C Runtime.
I imagine they speak of the need of quotes around arguments which contains spaces, and escaping the quotes inside arguments.
There is no such built-in function. You can write your own (translated from Python function).
public static string ListToCommandLine(IEnumerable<string> args)
{
return string.Join(" ", args.Select(a =>
{
if (string.IsNullOrEmpty(a))
return "\"\"";
var sb = new StringBuilder();
var needQuote = a.Contains(' ') || a.Contains('\t');
if (needQuote)
sb.Append('"');
var backslashCount = 0;
foreach (var c in a)
{
switch (c)
{
case '\\':
backslashCount++;
break;
case '"':
sb.Append(new string('\\', backslashCount * 2));
backslashCount = 0;
break;
default:
if (backslashCount > 0)
{
sb.Append(new string('\\', backslashCount));
backslashCount = 0;
}
sb.Append(c);
break;
}
if (backslashCount > 0)
sb.Append(new string('\\', backslashCount * (needQuote ? 2 : 1)));
if (needQuote)
sb.Append('"');
}
return sb.ToString();
}));
}

Censoring words in string[] by replacing

I am making a censor program for a game .dll I cannot figure out how to do this. I have a string[] of words and sentences. I have found out how to filter the words and block the messages. Right now I am trying to replace words with * the same length as a word. For example if someone said "fuck that stupid ass" it would come out as **** that stupid ***. Below is the code I am using
public void Actionfor(ServerChatEventArgs args)
{
var player = TShock.Players[args.Who];
if (!args.Text.ToLower().StartsWith("/") || args.Text.ToLower().StartsWith("/w") || args.Text.ToLower().StartsWith("/r") || args.Text.ToLower().StartsWith("/me") || args.Text.ToLower().StartsWith("/c") || args.Text.ToLower().StartsWith("/party"))
{
foreach (string Word in config.BanWords)
{
if (player.Group.HasPermission("caw.staff"))
{
args.Handled = false;
}
else if (args.Text.ToLower().Contains(Word))
{
switch (config.Action)
{
case "kick":
args.Handled = true;
TShock.Utils.Kick(player, config.KickMessage, true, false);
break;
case "ignore":
args.Handled = true;
player.SendErrorMessage("Your message has been ignored for saying: {0}", Word);
break;
case "censor":
args.Handled = false;
var wordlength = Word.Length;
break;
case "donothing":
args.Handled = false;
break;
}
}
}
}
else
{
args.Handled = false;
}
}
public string[] BanWords = { "fuck", "ass", "can i be staff", "can i be admin" };
Some places have code something like this under my case "censor"
Word = Word.Replace(Word, new string("*", Word.Length));
However I always get an error cannot convert string to char and cannot figure out else to do.
The compiler is telling you the problem; the overload of String you want takes a char and int, not a string and int.
It's trying to convert the * from a string to a char. Replace the double quotes " with a single quote '.
For chars, use single quotes ' instead of double quotes " like this:
new string('*', Word.Length)
And in your code, you don't need to replace. Simply do:
Word = new string('*', Word.Length);

Storing an array of strings and outputting the result to CSV

The below code writes a list of chocolate to a CSV file:
using (StreamWriter outfile = new StreamWriter(#"C:\Users\Martin\Desktop\chocListWRITE.csv", true)) //true: append text to a file with StreamWriter. The file is not erased, but just reopened and new text is added to the end.
{
Chocolate _choc = new Chocolate();
string line = _choc.Serialize();
outfile.WriteLine(line);
Console.WriteLine(line);
}
in the console they are displayed as:
11111, Mars
22222, Bounty
33333, Snickes
But in the output CSV file they are displayed as:
11111, Mars 22222, Bounty 33333, Snickers
This is fine however IF POSSIBLE I would like the output file to store the results the same as the console, with a new line after each object. So for this I have tried to store them as an array and output that????
I am getting the error 'the file could not be written' index was outside the bounds of the array' with choc[arrayNo] = Escape(c._barcode.number);
//dummy data of list of chocolates assigned to 'Chocolates'
//serialize function
string[] fullList = new string[] { };
string[] choc = new string[] { };
int arrayNo = -1;
foreach (Choclate c in Choclates)
{
arrayNo = arrayNo + 1;
choc[arrayNo] = Escape(c._barcode.number);//returns barcode as string
choc[arrayNo + 1] = Escape(c._bar.barName);//returns name as string
}
return choc;
}
string Escape(String s)
{
StringBuilder sb = new StringBuilder();
bool needQuotes = false;
foreach (char c in s.ToArray())
{
switch (c)
{
case '"': sb.Append("\\\""); needQuotes = true; break;
case ' ': sb.Append(" "); needQuotes = true; break;
case ',': sb.Append(","); needQuotes = true; break;
case '\t': sb.Append("\\t"); needQuotes = true; break;
case '\n': sb.Append("\\n"); needQuotes = true; break;
default: sb.Append(c); break;
}
}
if (needQuotes)
return "\"" + sb.ToString() + "\"";
else
return sb.ToString();
}
What about using this?
return Choclates
.Select(i => Escape(i._barcode.number) + "," + Escape(i._bar.barName))
.ToArray();
This is a simple way to build a string array from a collection. You might have to add System.Linq to your usings.
Also, there's a few helpful methods you can use to make the whole job easier, for example, your Encode method can use this:
private static readonly char[] CharsToEncode = new []{'"', '\r', '\n', ',', '\t'};
string Escape(string str)
{
if (str.IndexOfAny(CharsToEncode) != -1)
{
return "\"" + str.Replace("\"", "\"\"") + "\"";
}
else
{
return str;
}
}
In any case, building a string array is completely unnecessary when you're using a StreamWriter. You only have to go through each of the chocolates and WriteLine each of them as needed. This will also add the endlines. If your results are unexpected, you're doing something else wrong. Are you perhaps using the WriteLine method only once instead of once per chocolate? Or, is your output shown in the browser? In that case, it would tend to be interpreted as HTML, which means it would appear there are no line breaks (you have to use <br /> for line breaks in HTML).
Even though you are setting arrayNo to negative 1 you will still get an off by one error in the rest of your code. MattC is correct, initialize the size of the array to be the same as the count of the Choclates list but then in addition, you need to change the line of code
choc[arrayNo + 1] = Escape(c._bar.barName);//returns name as string
Suppose you have an array that is 1 element then in your example, when you start inside the foreach loop, arrayNo is equal to 0:
var firstElement = choc[arrayNo] // arrayNo is 0 so an array of 1 item will be able to reference this
var secondElement = choc[arrayNo + 1] // choc is a 1 element array so you will get out of bounds exception
EDIT
Have you tried using "\r\n" instead of just "\n" for the newline character? That makes a difference on some systems.

C# Switch results to list then to comma separated values

How would I make a switch statement populate a list, or comma delimited string?
For example
switch(test)
{
case 0:
"test"
break;
case 1:
"test2"
break;
case 2:
"test3"
break;
}
So my program will go into this statement multiple times. So lets say it goes in there twice and has case 2 and case 1. I woulld like a string value containing the following:
string value = "test3, test2"
Looks like a List<string> would be ideal to hold your values, you can create a comma separated string from that using string.Join():
List<string> myList = new List<string>();
//add items
myList.Add("test2");
//create string from current entries in the list
string myString = string.Join("," myList);
By multiple times, you mean a loop? You can just have a string and concatenate the string using + operator, or you can just have a list and add to it everytime the case condition is satisfied.
But if you mean by conditional flow so that you want case 0, 1 and 2 to all be evaluated, then you can simply omit the break and do the same concatenation like I mentioned above.
private string strValue = string.Empty;
private string StrValue
{
get
{
return strValue ;
}
set
{
StrValue= string.Concat(strValue , ",", value);
}
}
switch(test)
{
case 0:
StrValue = "test"
break;
case1:
StrValue = "test2"
break;
case 2:
StrValue = "test3"
breakl
}
Where ever you used StrValue remove "," if "," comes in the last.
There's a couple ways you can do it, a very simple one is:
string csv = "";
while (yourCriteria) {
string value;
// insert code to get your test value
switch(test)
{
case 0:
value = "test";
break;
case1:
value = "test2";
break;
case 2:
value = "test3";
break;
}
csv += value + ", ";
}
csv = csv.Length > 0 ? csv.Substring(0, csv.Length-2) : "";
Use a loop and a StringBuilder. If you're doing repeated concatenation, StringBuilders are significantly more efficient than naive string concatenation with +.
StringBuilder sb = new StringBuilder();
for(...)
{
switch(test)
{
case 0:
sb.Append("test");
break;
case1:
sb.Append("test2");
break;
case 2:
sb.Append("test3");
break;
}
}

Categories

Resources