I have a class with four fields (DateTime, Enum, string, string). I want to serialize it to and from an XML element or a series of XML elements in a compact manner. For example, I might serialize it to something like this:
<i t='234233' a='3'><u>Username1</u><s1>This is a string</s1></i>
<i t='234233' a='4'><u>Username2</u><s1>This is a string</s1></i>
<i t='223411' a='1'><u>Username3</u><s1>This is a string</s1></i>
Where 'i' is each class instance, 't' is the DateTime ticks, 'a' is the enum value, and the elements are strings.
I'd prefer to not have a root element, but if I do have it, I'd like it to be as small as possible.
I've tried using XmlSerializer with the XmlWriterSettings class but I can't get rid of the namespaces and root element.
What's the best way of doing this? I'm not saving to a file, I'm reading and writing to strings in memory.
System.Xml.Linq
XElement xElem = new XElement("r");
for (int i = 0; i < 3; i++)
{
xElem.Add(
new XElement("i",
new XAttribute("t", "234233"),
new XAttribute("a", "3"),
new XElement("u", "UserName"),
new XElement("s1", "This is a string")
)
);
}
var str = xElem.ToString();
and to read
XElement xElem2 = XElement.Load(new StringReader(str));
foreach(var item in xElem2.Descendants("i"))
{
Console.WriteLine(item.Attribute("t").Value + " " + item.Element("u").Value);
}
PS:
You don't need to convert xElem to string in order to use that xml in memory
If your data is that simple, you can use XmlWriter directly:
class Data {
public DateTime Date { get; set; }
public int Code { get; set; }
public string First { get; set; }
public string Last { get; set; }
}
static void Main() {
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = false;
var elements = new[] {
new Data { Date = DateTime.Now, First = "Hello", Last = "World", Code = 2}
, new Data { Date = DateTime.UtcNow, First = "Quick", Last = "Brown", Code = 4}
};
using (var xw = XmlWriter.Create(sb, xws)) {
xw.WriteStartElement("root");
foreach (var d in elements) {
xw.WriteStartElement("i");
xw.WriteAttributeString("t", ""+d.Date);
xw.WriteAttributeString("a", "" + d.Code);
xw.WriteElementString("u", d.First);
xw.WriteElementString("s1", d.Last);
xw.WriteEndElement();
}
xw.WriteEndElement();
}
Console.WriteLine(sb.ToString());
}
Running this program produces the following output (I added line breaks for clarity; they are not in the output):
<root>
<i t="2/9/2012 3:16:56 PM" a="2"><u>Hello</u><s1>World</s1></i>
<i t="2/9/2012 8:16:56 PM" a="4"><u>Quick</u><s1>Brown</s1></i>
</root>
You need that root element if you would like to read the information back. The most expedient way would be using LINQ2XML:
var xdoc = XDocument.Load(new StringReader(xml));
var back = xdoc.Element("root").Elements("i").Select(
e => new Data {
Date = DateTime.Parse(e.Attribute("t").Value)
, Code = int.Parse(e.Attribute("a").Value)
, First = e.Element("u").Value
, Last = e.Element("s1").Value
}
).ToList();
I'd just use a StringBuilder, personally.
If size is your #1 concern, consider json or yaml instead of XML.
You'll need to implement your own serializer, I believe, which will be a pain. That, or you could manually strip out what you don't need after serializing, which could work.
If size is your concern, you should be serializing to binary using BinaryFormatter. You can always base-64 encode it if you need to store it as a string. (BinaryFormatter works almost just like XmlSerializer, except the output is raw binary, rather than nicely formatted XML.)
Related
I am getting data with the help of api for credit card information. The data I got is as follows.
<<?xml version="1.0" encoding="ISO-8859-9"?>
<CC5Response>
<ProcReturnCode>00</ProcReturnCode>
<Response>Approved</Response>
<Extra>
<PAN1LABEL>PRICE</PAN1LABEL>
<PAN2INDEXORDER>1</PAN2INDEXORDER>
<PAN2STATUS>NO</PAN2STATUS>
<SAFEKEYLASTMODIFIED>11/22/333</SAFEKEYLASTMODIFIED>
<PAN2INDEXACCOUNTCLOSURE>00</PAN2INDEXACCOUNTCLOSURE>
<PAN1STATUS>NO</PAN1STATUS>
<PAN1INDEXACCOUNTCLOSURE>00</PAN1INDEXACCOUNTCLOSURE>
<PAN2EXPIRY>00.1111</PAN2EXPIRY>
<PAN1OWNER>JOHN DAVIS</PAN1OWNER>
<PAN2LABEL>PRODUCT BUY</PAN2LABEL>
<PAN2DESCRIPTION>DESCRIPION</PAN2DESCRIPTION>
<PAN1>1111 22** **** 3333</PAN1>
<PAN2>1111 22** **** 3333</PAN2>
<PAN2OWNER>JOHN DAVIS</PAN2OWNER>
<PAN1EXPIRY>02.2025</PAN1EXPIRY>
<NUMBEROFPANS>3</NUMBEROFPANS>
<PAN1INDEXORDER>1</PAN1INDEXORDER>
<PAN1DESCRIPTION>PRODUCT NO</PAN1DESCRIPTION>
</Extra>
</CC5Response>
I need to get the data with the whole word "PAN" in it. I'll add this to the user's saved card info screen. I tried a few times but it didn't work. Here are the codes i tried
List<string> list = new List<string>();
list.add(responseFromServer); //"responseFromServer" data from api
string[] getPanArray = list.ToArray();
List<string> panlist = new List<string>();
string pan = "PAN";
foreach (var item in getPanArray)
{
if (item.Contains(pan))
{
panlist.Add(item);
}
}
Latter
List<string> list = new List<string>();
list.Add(responseFromServer);
String[] test = list.ToArray();
String text = "PAN";
String[] result = test.Where(s =>
{
Boolean isInThere = true;
Char[] textArray = text.ToCharArray();
foreach (Char c in textArray)
{
if (!s.Contains(c))
isInThere = false;
}
return isInThere;
}).ToArray();
So, since responseFromServer is a string, first, parse the string into an XDocument.
using System.Xml.Linq;
...
var response = XDocument.Parse(responseFromServer);
Then you can use LINQ to filter out the elements you are interested in.
using System.Linq;
...
var justPans = response
.Element("CC5Response")
.Element("Extra")
.Elements().Where(e => e.Name.LocalName.Contains("PAN"));
Gives you an IEnumerable of XElement for just the PAN elements. Then you can extract the contexts of valid text elements.
var panContents = justPans
.Select(e => e.FirstNode)
.OfType<XText>()
.Select(t => t.Value)
.ToList();
This is demoed here.
That is a horribly formatted XML, and its contents do not make any sense. For example, it says it has 3 PANs:
<NUMBEROFPANS>3</NUMBEROFPANS>
But I only see two.
Furthermore, what is the purpose of having variable starting index and then have it the same for two entries?
<PAN1INDEXORDER>1</PAN1INDEXORDER>
In short, this is next to impossible to parse easily using direct deserialization into classes which is how you are supposed to parse XML in C#.
The data from the API should be reformatted by the host to look like this before they send it to you:
<Extra>
<Items>
<Item>
<INDEXORDER>1</INDEXORDER>
<LABEL>PRICE</LABEL>
<STATUS>NO</STATUS>
<INDEXACCOUNTCLOSURE>00</INDEXACCOUNTCLOSURE>
<OWNER>JOHN DAVIS</OWNER>
<PAN>1111 22** **** 3333</PAN>
<EXPIRY>02.2025</EXPIRY>
<DESCRIPTION>PRODUCT NO</DESCRIPTION>
</Item>
<Item>
<INDEXORDER>1</INDEXORDER>
<LABEL>PRODUCT BUY</LABEL>
<STATUS>NO</STATUS>
<INDEXACCOUNTCLOSURE>00</INDEXACCOUNTCLOSURE>
<OWNER>JOHN DAVIS</OWNER>
<PAN>1111 22** **** 3333</PAN>
<EXPIRY>00.1111</EXPIRY>
<DESCRIPTION>DESCRIPION</DESCRIPTION>
</Item>
</Items>
<SAFEKEYLASTMODIFIED>11/22/333</SAFEKEYLASTMODIFIED>
</Extra>
In order for it to be easily parsed. Then it is a simple matter of making model classes:
public class Item
{
public int IndexOrder;
public string Label;
public string Status;
public int IndexAccountClosure;
public string Owner;
public string PAN;
public string Expiry;
public string Description;
}
public class ExtraData
{
public Item[] Items;
public string SafeKeyLastModified;
}
public class CCSResponse
{
public int ProcReturnCode;
public string Response;
public ExtraData Extra;
}
And then letting XMLSerializer do its job:
string XMLTextFromAPI = YourAPICallHere();
CCSResponse data;
XmlSerializer serializer = new XmlSerializer(typeof(CCSResponse));
using (TextReader reader = new StringReader(XMLTextFromAPI)) {
data = (CCSResponse)serializer.Deserialize(reader);
}
Afterwards you can just access the data like this:
foreach (Item i in data.Extra.Items) {
// do whatever you need with those PANs
}
Anything other than that, and you are asking for trouble both in writing and in maintaining that code later.
TL;DR — sometimes a proper solution is not to accept to work with garbage data that you are given and ask them to change it, at least that's what I would do if I were in your place.
I am sure someone else will post an actual answer on how to parse that garbage data which will probably involve regex or LINQ in some way.
I have this JSON:
{"id":1,"name":"Alabama"}
{"id":2,"name":"Alaska"}
{"id":3,"name":"Arizona"}
{"id":4,"name":"Arkansas"}
and this code:
string[] testDataFiles = new string[] { StatesOneFileName, StatesTwoFileName, ContinentsFileName };
foreach (var testFile in testDataFiles)
{
using (StreamReader r = new StreamReader(testFile))
{
string json = r.ReadToEnd();
dynamic jsonObjectArray = JsonConvert.DeserializeObject(json);
foreach (var item in jsonObjectArray)
{
expectedPartitions.Add(item.name);
}
}
}
When I run this, however, I get this error:
Additional text encountered after finished reading JSON content: {. Path '', line 2, position 0.
I've read a few StackOverflow suggestions to encase the test data in [], but unfortunately I cannot edit the json file itself. Is there a way to wrap something in my code such that it reads the correct way? Thanks.
Assuming a file that contains a json object per line, as follows:
{"id":1,"name":"Alabama"}
{"id":2,"name":"Alaska"}
{"id":3,"name":"Arizona"}
{"id":4,"name":"Arkansas"}
You can create the following model for your data:
public class State
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name{ get; set; }
}
And then create a simple method that reads the file line by line, as follows:
public static IEnumerable<State> GetStates(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentException("Path to json file cannot be null or whitespace.", nameof(path));
}
if (!File.Exists(path))
{
throw new FileNotFoundException("Could not find json file to parse!", path);
}
foreach (string line in File.ReadLines(path).Where(x => !string.IsNullOrWhiteSpace(x)))
{
State state = JsonConvert.DeserializeObject<State>(line);
yield return state;
}
}
Using the code:
string path = ".....";
// get the list of State objects...
List<State> states = GetStates(path).ToList();
// or just the list of state names
List<string> stateNames = GetStates(path).Select(x => x.Name).ToList();
If this is your json file, it's not correctly formatted... You have multiple json objects in it, but it's neither enclosed in [] to show it's an array of json objects, nor are they delimited by comma.
The best thing you can do here, is to read line at a time, and convert it to json object, since it seems that each line represents one json object.
string json = "start";
using (StreamReader r = new StreamReader(testFile)) {
while (!String.IsNullOrWhiteSpace(json)){
json = r.ReadLine();
dynamic jsonObject = JsonConvert.DeserializeObject(json);
//DO YOUR MAGIC HERE
}
}
Here's one way you could easily fix the syntax errors in the json from that file, without modifying your existing code too much:
foreach (var testFile in testDataFiles)
{
// read the contents of the file with invalid json format, and build a new string named cleanJson with the fixed file contents
string[] fileLines = File.ReadAllLines(testFile);
string cleanJson = "["; // add an opening array bracket to make it valid json syntax
for (int i = 0; i < fileLines.Length; i++)
{
cleanJson += fileLines[i];
if (i < fileLines.Length - 1)
cleanJson += ","; // add a comma to the end of each line (except the last) to make it valid json syntax
}
cleanJson += "]"; // add a closing array bracket to make it valid json syntax
dynamic jsonObjectArray = JsonConvert.DeserializeObject(cleanJson);
foreach (var item in jsonObjectArray)
{
expectedPartitions.Add(item.name);
}
}
I'm developing a web service that will receive a XML string and will compare with the internalName string. I'm using LINQ to parse the XML (and I think I'm doing it correctly) but I'm not sure how to compare the "value" withinternalName, per example.
[WebMethod]
public string WMCompare (string xml, string internalName)
{
XDocument xmlDoc = XDocument.Load(xml);
var result = from ele in xmlDoc.Descendants("property")
select new
{
key = (string)ele.Element("key"),
value = (string)ele.Element("value")
};
foreach (var i in result)
{
}
}
}
Thank you for your attention and I'm sorry about the newbie question. It's my first time working with XML.
Considering that you are comparing string with value:
var newResult = result.Where(r => r.value.Equals(internalName))
Alternatively, you may also compare while parsing your XML:
var result1 = from ele in doc.Descendants("property")
where ele.HasElements && ele.Element("value") != null && ele.Element("value").Equals(internalName)
select new
{
key = (string)ele.Element("key"),
value = (string)ele.Element("value")
};
I have thousands of lines of data in a text file I want to make easily searchable by turning it into something easier to search (I am hoping an XML or another type of large data structure, though I am not sure if it will be the best for what I have in mind).
The data looks like this for each line:
Book 31, Thomas,George, 32, 34, 154
(each book is not unique, they are indexes so book will have several different entries of whom is listed in it, and the numbers are the page they are listed)
So I am kinda of lost on how to do this, I would want to read the .txt file, trim out all the spaces and commas, I basically get how to prep the data for it, but how would I programmatically make that many elements and values in xml or populate some other large data structure?
If your csv file does not change too much and the structure is stable, you could simply parse it to a list of objects at startup
private class BookInfo {
string title {get;set;}
string person {get;set;}
List<int> pages {get;set;}
}
private List<BookInfo> allbooks = new List<BookInfo>();
public void parse() {
var lines = File.ReadAllLines(filename); //you could also read the file line by line here to avoid reading the complete file into memory
foreach (var l in lines) {
var info = l.Split(',').Select(x=>x.Trim()).ToArray();
var b = new BookInfo {
title = info[0],
person = info[1]+", " + info[2],
pages = info.Skip(3).Select(x=> int.Parse(x)).ToList()
};
allbooks.Add(b);
}
}
Then you can easily search the allbooks list with for instance LINQ.
EDIT
Now, that you have clarified your input, I adapted the parsing a little bit to better fit your needs.
If you want to search your booklist by either the title or the person more easily, you can also create a lookup on each of the properties
var titleLookup = allbooks.ToLookup(x=> x.title);
var personLookup = allbooks.ToLookup(x => x.person);
So personLookup["Thomas, George"] will give you a list of all bookinfos that mention "Thomas, George" and titleLookup["Book 31"] will give you a list of all bookinfos for "Book 31", ie all persons mentioned in that book.
If you want the CSV file to make easily searchable by turning it into something easier to search, you can convert it to DataTable.
if you want data , you can use LINQ to XML to search
The following class generates both DataTable or Xml data format. You can pass delimeter ,includeHeader or use the default:
class CsvUtility
{
public DataTable Csv2DataTable(string fileName, bool includeHeader = false, char separator = ',')
{
IEnumerable<string> reader = File.ReadAllLines(fileName);
var data = new DataTable("Table");
var headers = reader.First().Split(separator);
if (includeHeader)
{
foreach (var header in headers)
{
data.Columns.Add(header.Trim());
}
reader = reader.Skip(1);
}
else
{
for (int index = 0; index < headers.Length; index++)
{
var header = "Field" + index; // headers[index];
data.Columns.Add(header);
}
}
foreach (var row in reader)
{
if (row != null) data.Rows.Add(row.Split(separator));
}
return data;
}
public string Csv2Xml(string fileName, bool includeHeader = false, char separator = ',')
{
var dt = Csv2DataTable(fileName, includeHeader, separator);
var stream = new StringWriter();
dt.WriteXml(stream);
return stream.ToString();
}
}
example to use:
CsvUtility csv = new CsvUtility();
var dt = csv.Csv2DataTable("f1.txt");
// Search for string in any column
DataRow[] filteredRows = dt.Select("Field1 LIKE '%" + "Thomas" + "%'");
//search in certain field
var filtered = dt.AsEnumerable().Where(r => r.Field<string>("Field1").Contains("Thomas"));
//generate xml
var xml= csv.Csv2Xml("f1.txt");
Console.WriteLine(xml);
/*
output of xml for your sample:
<DocumentElement>
<Table>
<Field0>Book 31</Field0>
<Field1> Thomas</Field1>
<Field2>George</Field2>
<Field3> 32</Field3>
<Field4> 34</Field4>
<Field5> 154</Field5>
</Table>
</DocumentElement>
*/
How can I transform the following XML into a List<string> or String[]:
<Ids>
<id>1</id>
<id>2</id>
</Ids>
It sounds like you're more after just parsing rather than full XML serialization/deserialization. If you can use LINQ to XML, this is pretty easy:
using System;
using System.Linq;
using System.Xml.Linq;
public class Test
{
static void Main()
{
string xml = "<Ids><id>1</id><id>2</id></Ids>";
XDocument doc = XDocument.Parse(xml);
var list = doc.Root.Elements("id")
.Select(element => element.Value)
.ToList();
foreach (string value in list)
{
Console.WriteLine(value);
}
}
}
In fact the call to Elements could omit the argument as there are only id elements, but I thought I'd demonstrate how to specify which elements you want.
Likewise I'd normally not bother calling ToList unless I really needed a List<string> - without it, the result is IEnumerable<string> which is fine if you're just iterating over it once. To create an array instead, use ToArray.
Here is a way using XmlDocument :
// A string containing the XML data
string xml = "<Ids><id>1</id><id>2</id></Ids>";
// The list you want to fill
ArrayList list = new ArrayList();
XmlDocument doc = new XmlDocument();
// Loading from a XML string (use Load() for file)
doc.LoadXml(xml);
// Selecting node using XPath syntax
XmlNodeList idNodes = doc.SelectNodes("Ids/id");
// Filling the list
foreach (XmlNode node in idNodes)
list.Add(node.InnerText);
With any type of collection.
For example :
<Ids>
<id>C:\videotest\file0.txt</id>
<id>C:\videotest\file1.txt</id>
</Ids>
class FileFormat
{
public FileFormat(string path)
{
this.path = path;
}
public string FullPath
{
get { return Path.GetFullPath(path); }
}
public string FileName
{
get { return Path.GetFileName(path); }
}
private string path;
}
List<FileFormat> Files =
selectedNode
.Descendants("Ids")
.Elements("Id")
.Select(x => new FileFormat(x.Value))
.Where(s => s.FileName!=string.Empty)
.ToList();
the code to convert a string array to xml
items is a string array
items.Aggregate("", (current, x) => current + ("<item>" + x + "</item>"))
This sample will work with the .NET framework 3.5:
System.Xml.Linq.XElement element = System.Xml.Linq.XElement.Parse("<Ids> <id>1</id> <id>2</id></Ids>");
System.Collections.Generic.List<string> ids = new System.Collections.Generic.List<string>();
foreach (System.Xml.Linq.XElement childElement in element.Descendants("id"))
{
ids.Add(childElement.Value);
}
This sample will work with the .NET framework 4.0:
into a List
List<string> Ids= new List<string>();
Ids= selectedNode.Descendants("Ids").Elements("Id").Select(> x=>x.Value).Where(s =>s!= string.Empty).ToList();
into a string []
string[] Ids= thisNode
.Descendants("Ids") // Ids element
.Elements("Id") // Id elements
.Select(x=>x.Value) // XElement to string
.Where(s =>s!= string.Empty) // must be not empty
.ToArray(); // string to string []
You can use Properties class.
Properties prop = new Properties();
prop.loadFromXML(stream);
Set set = prop.keySet();
You can than access Strings from set for each key. Key is element name of xml.
Here's a way to get typed array from xml by using DataSets. (in this example array of doubles)
DataSet dataSet = new DataSet()
DoubleArray doubles = new DoubleArray(dataSet,0);
dataSet.ReadXml("my.xml");
double a = doubles[0];
public class DoubleArray
{
DataSet dataSet;
int tableIndex;
public DoubleArray(DataSet dataSet,int tableIndex)
{
this.dataSet=dataSet;
this.tableIndex=tableIndex;
}
public double this[int index]
{
get
{
object ret = dataSet.Tables[tableIndex].Rows[index];
if(ret is double?)
return (ret as double?).Value;
else
return double.Parse(ret as string);
}
set
{
object out = dataSet.Tables[tableIndex].Rows[index];
if(out is double?)
dataSet.Tables[tableIndex].Rows[index] = (double?)value;
else
dataSet.Tables[tableIndex].Rows[index] = value.ToString();
}
}
}
Of course parsing doubles and converting them back to strings all the time might be considered as blasphemy by some programmers... Even for me it was hard not to think about such waste of resources... but I guess sometimes it's better to just turn another away.. don't stress it :)