How to write unescaped quoted values - c#

Does CsvHelper not support quoted unescaped output or is there something wrong with my writer configuration?
Input:
Jake|"B" Street
CsvHelper reader configuration:
var readerConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = "|",
TrimOptions = TrimOptions.Trim,
IgnoreBlankLines = true,
Mode = CsvMode.NoEscape
};
After record is read into memory, the value with double quotes is unescaped as expected: "B" Street
However, when I write records to CSV file if ShouldQuote = args => true is used without CsvMode.NoEscape double quotes are escaped and if I add CsvMode.NoEscape the output is not quoted at all.
writerConfiguration = new CsvConfiguration(CultureInfo.InvariantCulture)
{
ShouldQuote = args => true,
Mode = CsvMode.NoEscape
};
Output with CsvMode.NoEscape: "B" Street // Output not quoted and not escaped.
Output without CsvMode.NoEscape: """B"" Street" // Double quotes escaped.
Any ideas on how to write unescaped quoted values with CsvHelper?

This feels a little hacky, but I was able to do what I think you are trying to do with CsvMode.NoEscape and a custom string converter to add the outer quotes. Another option would be to use Convert in a ClassMap to add the outer quotes on a column by column basis.
void Main()
{
var records = new List<Foo>
{
new Foo { Id = 1, Name = "\"B\" Street" },
};
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Mode = CsvMode.NoEscape
};
using (var csv = new CsvWriter(Console.Out, config))
{
csv.Context.TypeConverterCache.AddConverter<string>(new OuterQuoteConverter());
csv.WriteRecords(records);
}
}
public class OuterQuoteConverter : StringConverter
{
public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
{
var quoted = "\"" + (string)value + "\"";
return base.ConvertToString(quoted, row, memberMapData);
}
}
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}

Related

Adding double quotes to string while writing to csv c#

I am trying to write data to csv file. I want the data to be displayed in double quotes when opened in notepad, but it shouldn't show any double quotes when I open the file in csv.
following is the code that I am using but, it doesn't give the result.
csvFile.WriteField("\"" +data.FirstName == null ? string.Empty : data.FirstName + "\"");
Can someone help me out with this?
The problem is you are trying to fight CsvHelper's quoting. According to the RFC 4180 specifications.
Fields containing line breaks (CRLF), double quotes, and commas
should be enclosed in double-quotes. For example:
"aaa","b CRLF
bb","ccc" CRLF
zzz,yyy,xxx
If double-quotes are used to enclose fields, then a double-quote
appearing inside a field must be escaped by preceding it with
another double quote. For example:
"aaa","b""bb","ccc"
So when you add double quotes to the field, CsvHelper recognizes that the whole field needs to be enclosed in double quotes and the added quotes need to be escaped with another double quote. That is why you end up with 3 double quotes.
CsvHelper has a configuration function where you can tell it when it should quote a field. #JoshClose already has an answer here for wrapping all fields in double quotes. I'll update it for the current version of CsvHelper.
void Main()
{
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
new Foo { Id = 2, Name = "two" },
};
using (var writer = new StringWriter())
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.Configuration.ShouldQuote = (field, context) => true;
csv.WriteRecords(records);
writer.ToString().Dump();
}
}
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
If you still wanted to add the double quotes yourself, you could turn off CsvHelper's double quoting.
csv.Configuration.ShouldQuote = (field, context) => false;
Breaking change for Version 20.0.0 and later
Changed CsvConfiguration to a read only record to eliminate threading issues.
You would need to create CsvConfiguration, set ShouldQuote on initialization and then pass it to the CsvWriter.
void Main()
{
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
new Foo { Id = 2, Name = "two" },
};
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
ShouldQuote = (field, context) => true
};
using (var writer = new StringWriter())
using (var csv = new CsvWriter(writer, config))
{
csv.WriteRecords(records);
writer.ToString().Dump();
}
}
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
I made code a bit more readable by placing the assignment of firstName to a variable that will be passed later. I used a string escape to add a quote to the final result. Now you can pass it to your WriteField method.
More information about string escape can be found: here and here.
var firstName = data.FirstName == null ? string.Empty : data.FirstName;
var firstNameWithQuoutes = $#"""{firstName}""";
Using the CsvConfiguration, you can change the CSV file configuration. To add the double quotes set the ShouldQuote=true, and to remove set ShouldQuote=false.
Plz try this in the latest version, I tried it in V28, and its works for me
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
ShouldQuote = (field) => false //Set the false to remove the double quotes
};
using (StreamWriter _streamWriter = new StreamWriter(fileNamePath))
{
using (var _csvWriter = new CsvWriter(_streamWriter, config))
{
await _csvWriter.WriteRecordsAsync(models);
}
}

Collection in C# Class Library

I wrote a simple .dll class library C#. My file takes a values of my main program and create a html file with this values. Look at my code: (I am describing it so that you can check if I have a mistake)
public class MyClass1 { public void HTMLGen(int number, string name)
{
var html1 = string.Format("<p>This is number: {0} and this is name: {1}</p>", number, name);
var xDocument = new XDocument(
new XDocumentType("html", null, null, null),
new XElement("html",
new XElement("head"),
new XElement("body",
XElement.Parse(html1))));
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true,
IndentChars = "\t"
};
using (var writer = XmlWriter.Create(#"C:\Users\Desktop\test.html", settings))
{
xDocument.WriteTo(writer);
}
} }
Okay, this code works well. so I need add to collection. I create a new class-file in this .dll. This is my new class:
public class Collection : IList {
public int value_one { get; set; }
public int value_two { get; set; }}
and I added this parameter to my class:
public class MyClass1 { public void HTMLGen(int number, string name, IList<Collection> collection)
so I create a new 'var html2' and I want to add this to XElement.Parse. This is my whole code:
public class MyClass1 { public void HTMLGen(int number, string name, IList<Collection> collection) {
var html1 = string.Format("<p>This is number: {0} and this is name: {1}</p>", number, name);
var html2 = string.Format("<p> /* value_one and value_two from Collection */ </p>");
var xDocument = new XDocument(
new XDocumentType("html", null, null, null),
new XElement("html",
new XElement("head"),
new XElement("body",
XElement.Parse(html1), XElement.Parse(html2))));
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true,
IndentChars = "\t"
};
using (var writer = XmlWriter.Create(#"C:\Users\Desktop\test.html", settings))
{
xDocument.WriteTo(writer);
} } }
and please, look at my comment. I don't know, how Can I add this values of my collection to this var html2.
Firstly, I don't understand why you would need to create a class named Collection with two integers. You can either use a int Array or list. Or may be a structure would be the best thing to use if you have not just int but other datatypes.
Lets suppose you need that class, then why should it inherit IList? I don't see any usage of it at all.
Anyway, just to answer your question:
var html2 = string.Format("<p> /* {0} and {1} from Collection */ </p>", collection.value_one, collection.value_two);
Or, if you have different types for your values, just convert it to string to be safe.
var html2 = string.Format("<p> /* {0} and {1} from Collection */ </p>", collection.value_one.ToString(), collection.value_two.ToString());
You can use Interpolated Strings to avoid long String.Format method usage like below.
var html1 = $"<p>This is number: {number} and this is name: {name}</p>");
var html2 = $"<p> This is first value: {collection[0]} and this is second value: {collection[1]}</p>";
Maybe you are trying to achive this?
public class MyClass1
{
public void HTMLGen(int number, string name, IList<int> collection)
{
var html1 = string.Format("<p>This is number: {0} and this is name: {1}</p>", number, name);
string html2 = "";
foreach (var item in collection)
{
html2 += item + " ";
}
html2 = "<p>" + html2 + "</p>";
//The rest of the code
}
}
usage:
MyClass1 myClass1 = new MyClass1();
List<int> numbers = new List<int> { 4, 3, 21, 123, 6 };
myClass1.HTMLGen(10, "name", numbers);

C# Trying to split a string to get json object value

I am trying to split a string to get json object value - I have text values with numerous lines in the format:
new Car() { Id = 1, Year = 1926, Make = "Chrysler", Model = "Imperial", ImageUrl = "{"data":{"images":[{"thumb_url":"https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcRPe4CygIW-MuZL5jl77wlgXXK5_ANyC9l1X4QqLizCOkaVAlRe","image_url":"http://imperialclub.org/Yr/1926/photos/Phaeton2Big.jpg","width":1632,"height":1032}]},"error_code":0,"error":false,"message":"1 images(s) available"}" },
new Car() { Id = 2, Year = 1950, Make = "Hillman", Model = "Minx Magnificent", ImageUrl = "{"data":{"images":[{"thumb_url":"https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcScVsGEeRBh6xZYXr6Gm35Sk5ecSlk_ax3qZmoGRAtBbZC8vJZ9","image_url":"http://i.ebayimg.com/images/g/gcIAAOSwKadXPeLs/s-l300.jpg","width":300,"height":225}]},"error_code":0,"error":false,"message":"1 images(s) available"}" },
new Car() { Id = 3, Year = 1954, Make = "Chevrolet", Model = "Corvette", ImageUrl = "{"data":{"images":[{"thumb_url":"https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcSdZntu4tgWrZrxwqeuKlteCP9vJGnqUlmNq5JF1bBCf-EJy5r8","image_url":"http://momentcar.com/images/chevrolet-corvette-1954-1.jpg","width":1000,"height":600}]},"error_code":0,"error":false,"message":"1 images(s) available"}" },
What I would really like is to get them in the format:
new Car() { Id = 1, Year = 1926, Make = "Chrysler", Model = "Imperial", ImageUrl = "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcRPe4CygIW-MuZL5jl77wlgXXK5_ANyC9l1X4QqLizCOkaVAlRe" },
new Car() { Id = 2, Year = 1950, Make = "Hillman", Model = "Minx Magnificent", ImageUrl = "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcScVsGEeRBh6xZYXr6Gm35Sk5ecSlk_ax3qZmoGRAtBbZC8vJZ9" },
new Car() { Id = 3, Year = 1954, Make = "Chevrolet", Model = "Corvette", ImageUrl = "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcSdZntu4tgWrZrxwqeuKlteCP9vJGnqUlmNq5JF1bBCf-EJy5r8" },
I know I can use JObject.Parse(data); to parse the json value - but just tring to get to it is becoming a bit of a nightmare. Is there a better way of doing this?
What I have so far:
static void Main(string[] args)
{
using (StreamWriter writer = new StreamWriter(#"c:\Data\temp\output.txt")) // file to write to
{
using (StreamReader reader = new StreamReader(#"c:\Data\temp\test.txt")) //file to read from
{
string line;
while (reader.ReadLine() != null)
{
line = reader.ReadLine();
string[] words = JsonSplitString(line);
string json = words[1];
writer.WriteLine("{0}", json);
}
}
}
}
static string[] JsonSplitString(string data)
{
return data.Split(new string[] { "ImageUrl" }, StringSplitOptions.None);
}
However I am getting a NullReferenceException - even though a string is being passed in to the JsonSplitString method.
You are calling reader.Readline() twice: once for the comparison and then again inside your loop. You are actually skipping every other line. And what is probably happening is that you are reaching the end of your file and then calling reader.Readline() again, which is null. Try this instead:
line = reader.ReadLine();
while (line != null)
{
string[] words = JsonSplitString(line);
string json = words[1];
writer.WriteLine("{0}", json);
line = reader.ReadLine();
}
using System;
using Newtonsoft.Json.Linq;
namespace JsonExperiments
{
class Program
{
static void Main(string[] args)
{
ExecuteEmployeeSearch();
Console.ReadLine();
}
static void ExecuteEmployeeSearch()
{
// mockup JSON that would be returned from API
string sampleJson = "{\"results\":[" +
"{\"employeename\":\"name1\",\"employeesupervisor\":\"supervisor1\"}," +
"{\"employeename\":\"name2\",\"employeesupervisor\":\"supervisor1\"}," +
"{\"employeename\":\"name3\",\"employeesupervisor\":[\"supervisor1\",\"supervisor2\"]}" +
"]}";
// Parse JSON into dynamic object, convenient!
JObject results = JObject.Parse(sampleJson);
// Process each employee
foreach (var result in results["results"])
{
// this can be a string or null
string employeeName = (string)result["employeename"];
// this can be a string or array, how can we tell which it is
JToken supervisor = result["employeesupervisor"];
string supervisorName = "";
if (supervisor is JValue)
{
supervisorName = (string)supervisor;
}
else if (supervisor is JArray)
{
// can pick one, or flatten array to a string
supervisorName = (string)((JArray)supervisor).First;
}
Console.WriteLine("Employee: {0}, Supervisor: {1}", employeeName, supervisorName);
}
}
}
}

Manage complex string array in C#

I want to use a string array stored in the web.config to easily change its values, this is in the format: full_w=670|small_w=100,q=low|tiny_h=30,c=true. Each template is split by the | (pipe) and then each of those sets comprises of a name (left of _) and its corresponding values (right of _), the values can be several and each separated by the , (comma). I think this possibly qualifies for a 3D array, I just can't seem to get an easy way to read this in a sensible manner. Any ideas or solutions as to the best way to read/manage the data from this string?
Basically, in the end I want to be able to call the template small and read its values which in this case are width=100 and quality=low.
Here's the function I wrote to parse one of these settings strings:
public static Dictionary<string, Dictionary<string, string>> getSettings(string settingsStr)
{
return settingsStr.Split('|').ToDictionary(
template => template.Split('_')[0],
template => template.Split('_')[1].Split(',').ToDictionary(
setting => setting.Split('=')[0],
setting => setting.Split('=')[1]));
}
It just uses a lot of string .Splitting and .ToDictionarying.
Here's the test, showing that it works:
var result = getSettings("full_w=670|small_w=100,q=low|tiny_h=30,c=true");
/*
result = {
[ "full" => [ "w" => "670" ] ]
[ "small" => [ "w" => "100", "q" => "low" ] ]
[ "tiny" => [ "h" => "30", "c" => "true" ] ]
}
*/
To read the values w and q from template small, you can do this:
int width = int.Parse(result["small"]["w"]);
string quality = result["small"]["q"];
Edit: As an added bonus, if you want to convert the Dictionary<string, Dictionary<string, string>> back into a single settings sting, you can use this method:
public static string getSettingsStr(Dictionary<string, Dictionary<string, string>> settings)
{
return string.Join("|",
settings.Select(kvp =>
kvp.Key + "_" + string.Join(",",
kvp.Value.Select(setting =>
setting.Key + "=" + setting.Value))));
}
Use:
string settingsStr = getSettingsStr(result);
// settingsStr = "full_w=670|small_w=100,q=low|tiny_h=30,c=true"
If you want to check that a specific template or setting exists, then use the .ContainsKey() method:
// If I have "Dictionary<string, Dictionary<string, string>> settings;"
int width = -1;
string quality = null;
if (settings.ContainsKey("small"))
{
if (settings["small"].ContainsKey("w"))
width = int.Parse(settings["small"]["w"]);
if (settings["small"].ContainsKey("q"))
quality = settings["small"]["q"];
}
Have you considered using plain old XML Serialization with your own plain old C# objects. Here is an example:
public class Program
{
static void Main(string[] args)
{
var data = new MyConfig[2];
for (int i = 0; i < 2; i++)
{
data[i] = new MyConfig { Name = "Name" + i };
data[i].Properties = new MyConfigAttribute[]
{
new MyConfigAttribute { Name = "Property Name " + i, Value = "Property Value " + i },
new MyConfigAttribute { Name = "2nd Property Name " + i, Value = "2nd Property Value " + i },
};
}
var serializer = new XmlSerializer(typeof(MyConfig[]));
using (StreamWriter tw = File.CreateText(#"c:\temp\myconfig.xml"))
{
serializer.Serialize(tw, data);
}
using (StreamReader tw = File.OpenText(#"c:\temp\myconfig.xml"))
{
var readBack = serializer.Deserialize(tw);
}
Console.ReadLine();
}
[XmlRoot("MY_CONFIG")]
public class MyConfig
{
[XmlElement("NAME")]
public string Name { get; set; }
[XmlArray]
[XmlArrayItem(typeof(MyConfigAttribute))]
public MyConfigAttribute[] Properties { get; set; }
}
[XmlRoot("MY_CONFIG_ATTRIBUTE")]
public class MyConfigAttribute
{
[XmlElement("ATTRIBUTE_NAME")]
public string Name { get; set; }
[XmlElement("ATTRIBUTE_VALUE")]
public string Value { get; set; }
}
}
Basically, you create a class to store your individual attributes (MyConfigAttribute in this case), wrap it in another class to provide your name for a group of related attributes (MyConfig in this case), then use normal XML Serialization to write the settings out to an individual XML file, like this section of the code
var serializer = new XmlSerializer(typeof(MyConfig[]));
using (StreamWriter tw = File.CreateText(#"c:\temp\myconfig.xml"))
{
serializer.Serialize(tw, data);
}
You can read it back to objects again using this section of the code:
using (StreamReader tw = File.OpenText(#"c:\temp\myconfig.xml"))
{
var readBack = serializer.Deserialize(tw);
}
The advantage of this is:
It is simple to understand and use
You can add features to your custom class, e.g. to add values to the array of properties, thereby lending itself to wrapping a custom screen around it.
Look up C# XML Serialization on Google!

Reading specific lines in a .Log file

I have a log file that I am reading into different objects. One object starts at a Line that contains the words "Announce message" and the following lines contain the data that belongs to that message. This entry stops at a line that contains the word "Disposed".
I want to read all the data from between these 2 lines that, contains certain words.
Im currently using a Dictionary because the line with "Announce message" also contains a UID but the following lines contain the data for that UID.
How would you do that?
This is what i have come up with so far.
public static void P2PLogParser(List<FileInfo> fileList)
{
foreach (FileInfo fi in fileList)
{
//Læser alle linier i csv fil
foreach (var line in File.ReadAllLines(fi.FullName))
{
string MeterUID = GetMeterUID(line);
string MimHashcode = GetMimHashcode(line);
string FirmwareUploadStatus = GetFirmwareUploadStatus(line);
string IsKnown = GetIsKnown(line);
DateTime P2PTimeStamp = GetTimestamp(line);
if (IsMeterEntry(line) && !meters.ContainsKey(MeterUID))
{
string MeterNr = GetMeterUID(line).Replace("4B414D", "");
int meternr = int.Parse(MeterNr, System.Globalization.NumberStyles.HexNumber);
meters.Add(MeterUID, new Meter()
{
MeterUID = MeterUID,
MeterNR = meternr,
P2Pmeterentry = new List<P2PMeterEntry>()
});
}
if (IsMeterEntry(line))
{
P2PMeterEntry p2pmeter = new P2PMeterEntry
{
P2PTimeStamp = P2PTimeStamp,
MimHashcode = MimHashcode,
FirmwareUploadStatus = FirmwareUploadStatus,
IsKnown = IsKnown,
P2PMetersession = new List<P2PMeterSession>()
};
if (IsNoLongerMeterEntry(line))
{
string SessionLevel = GetLevel(line);
string SessionMessage = GetSessionMessage(line);
string Context = GetSessionContext(line);
P2PMeterSession MeterSession = new P2PMeterSession
{
SessionTimeStamp = P2PTimeStamp,
SessionLevel = SessionLevel,
SessionMessage = SessionMessage,
Context = Context
};
meterSession.Add(MeterSession);
}
meters[MeterUID].P2Pmeterentry.Add(p2pmeter);
}
}
}
}
and the IsMeterEntry and IsNoLongerMeterEntry
//IsMeterSession
public static bool IsMeterEntry(string text)
{
return text.ToLower().Contains("announce message received:");
}
public static bool IsNoLongerMeterEntry(string text)
{
return text.ToLower().Contains("context - disposed");
}
Implement a simple state machine with two states: IgnoreLine (initial state) and Announce.
for each line in log
if line contains "Announce message"
read UID
create a StringBuilder
set state=Announce
else if line contains "Disposed"
store the StringBuilder's content in the dictionary[uid]
set state=IgnoreLine
else if state==Announce and line contains "certain words"
append line to StringBuilder

Categories