Reading xml correctly, but order gets messed later - c#

I parse xml data from a file to a list of objects of this class:
public class VerificationSample
{
public double DesiredA { get; set; }
public double DesiredB { get; set; }
public double DesiredC { get; set; }
// ..
public void ReadXml(XmlReader reader)
{
reader.ReadToFollowing("VerificationSample");
this.DesiredA = (double)FileStructure.GetAttributeSafe(reader, "DesiredA", typeof(double));
this.DesiredB = (double)FileStructure.GetAttributeSafe(reader, "DesiredB", typeof(double));
this.DesiredC = (double)FileStructure.GetAttributeSafe(reader, "DesiredC", typeof(double));
this.ReferenceNumber = (int)FileStructure.GetAttributeSafe(reader, "ReferenceNumber", typeof(int));
// ..
}
}
I read this sample xml:
<VerificationSamples Count="4">
<VerificationSample DesiredA="0.000" DesiredB="50.000" DesiredC="0.000" ReferenceNumber="0"/>
<VerificationSample DesiredA="75.000" DesiredB="-3.000" DesiredC="0.000" ReferenceNumber="0"/>
<VerificationSample DesiredA="-30.000" DesiredB="0.000" DesiredC="0.000" ReferenceNumber="1"/>
<VerificationSample DesiredA="-2.000" DesiredB="-60.000" DesiredC="0.200" ReferenceNumber="1"/>
</VerificationSamples>
Using this routine:
public void LoadFromFile(string path)
{
XmlReader reader = XmlReader.Create(path);
// ..
reader.ReadToFollowing("VerificationSamples");
int count = (int)FileStructure.GetAttributeSafe(reader, "Count", typeof(int));
this.VerificationSamples.Clear();
for (int i = 0; i < count; i++)
{
VerificationSample newVerificationSample = new VerificationSample();
newVerificationSample.ReadXml(reader);
this.VerificationSamples.Add(newVerificationSample);
}
}
All is good so far and works like a charm. However, later when I execute another function where I try to access my VerificationSamples list to use some values of my objects, the order of the objects gets messed.. so first line in the xml (was the first object) but later it's the 3rd.. 2nd is the 1st.. 4th is the 2nd and so on (no obvious pattern). Does anyone imagine why???

Per this answer, List<T> guarantees both the order of insertion and the order of retrieval.
If the items are not coming out in the order in which they were added, there is some external force that is disrupting the order (sorting, perhaps).

If the file is not big,use XElement.
using System.Xml.Linq
XElement file=XElement.Load(#"your path");
foreach(var VerificationSample in file.Elements("VerificationSample"))
{
// do want you want XElement type VerificationSample in order at the file
}

This was happening due to a problem when retreiving data from the data base. It was fixed for now by adding a property in the xml OrderID that will be used as a reference for the order:
<VerificationSamples Count="4">
<VerificationSample DesiredA="0.000" DesiredB="50.000" DesiredC="0.000" ReferenceNumber="0" OrderID="1"/>
<VerificationSample DesiredA="75.000" DesiredB="-3.000" DesiredC="0.000" ReferenceNumber="0" OrderID="2"/>
<VerificationSample DesiredA="-30.000" DesiredB="0.000" DesiredC="0.000" ReferenceNumber="1" OrderID="3"/>
<VerificationSample DesiredA="-2.000" DesiredB="-60.000" DesiredC="0.200" ReferenceNumber="1" OrderID="4"/>
</VerificationSamples>
Anytime I retrieve the list from the data base, I just sort it by the OrderID. VerificationSamples.OrderBy(x => x.OrderID);

Related

Extracting mixed-order data of type xml that I get from API

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.

Working with XML under C# - Difficulties

it's me again. This time, having some issues with XML. I had everything working in VB.NET (I'll show all the code I used later) but now I'm developing something else for VB application except I'm using C# for it. Part of this involves reading an XML and populating something specific into a ListBox and then being able to click on it and get the attributes for use in other controls (description loads to a text box, etc, you'll see).
I can't seem to figure out XML for the life of me in C# however. In VB, I did it like this:
Dim games() As String = xml...<episode>.Select(Function(n) n.Value).ToArray
AvailableEpisodes.DataSource = games
Where "AvailableEpisodes" is the ListBox I wish to populate. This displayed the "This is a test" term: This is a test
And then this is the SelectedIndexChanged code:
Dim node As XElement = xml...<episode>.First(Function(n) n.Value = AvailableEpisodes.Text)
DescriptionTextBox.Text = node.#Description
AuthorTextBox.Text = node.#Author
generatedDownloadLink = node.#DownloadLink
generatedTechName = node.#TechName
IconImage.ImageLocation = node.#IconLoc
What exactly would be the C# equivalent of this? I already tried copy-pasting (just figured I'd try it) and a couple code converters and none of them seem to work.
The goal of this application will be to be able to double click on the selected index and load these strings into another window (which I can work out by attaching the nodes to a string variable) I just need to get started.
Code I've tried:
using (XmlReader reader = XmlReader.Create(testXml))
{
while (reader.Read())
{
if ((reader.NodeType == XmlNodeType.Element) && reader.Name == "episode")
{
listBox1.DataSource = reader.GetAttribute("TechName").ToList();
}
}
}
But that literally just outputs this: http://imgur.com/Naeabf9.png
Any extra information I'll toss in an edit or a reply
Thanks in advanced,
Mike
Its easy: ... corresponds to Descendants. <foo> corresponds to element name. #bar corresponds to attribute name.
var xml = XDocument.Load(path_to_xml);
var node = xml.Descendants("episode")
.First(n => n.Value == AvailableEpisodes.Text);
DescriptionTextBox.Text = (string)node.Attribute("Description");
AuthorTextBox.Text = (string)node.Attribute("Author");
generatedDownloadLink = (string)node.Attribute("DownloadLink");
generatedTechName = (string)node.Attribute("TechName");
IconImage.ImageLocation = (string)node.Attribute("IconLoc");
Note - if there is no matching node, then First will throw exception. Usually you should use FirstOrDefault in such case and then check node for null.
Is "This is a test" supposed to be one single item in the ListBox? If it is, your logic in the C# code is wrong. Indeed, reader.GetAttribute("TechName").ToList() will return an array like this
["T","h","i", "s", " ", "i", "s", " ", "t", "e", "s", "t"]
Therefore, the text gets broken up into many items in the ListBox.
To read all nodes in the xml documents into ListBox, you have to create a temporary list to hold all the results read from the xml, then at the end bind the list to ListBox listBox1.DataSource = results
Why don't you just use XML Serialization?
Assuming your XML looks something like this:
<EpisodeData>
<Episodes>
<Episode Description="..." Author="..." DownloadLink="..." ... />
<Episode Description="..." Author="..." DownloadLink="..." ... />
<Episode Description="..." Author="..." DownloadLink="..." ... />
</Episodes>
</EpisodeData>
Create classes in C# that represents the data. Something like this:
[Serializable]
public class EpisodeData
{
[XmlArray("Episodes")]
[XmlArrayItem(ElementName = "Episode")]
List<Episode> Episodes { get; set; }
}
[Serializable]
public class Episode
{
[XmlAttribute]
public string Description { get; set; }
[XmlAttribute]
public string Author { get; set; }
[XmlAttribute]
public string DownloadLink { get; set; }
...
}
Then you can deserialize and use the data like this:
EpisodeData data;
XmlSerializer serializer = new XmlSerializer(typeof(EpisodeData));
using (StreamReader sr = new StreamReader(fileName))
{
data = (EpisodeData)serializer.Deserialize(sr);
}
// Assuming you only want to see the description. If you want something else
// you might want to use a DataGrid to bind to each property in Episode or
// override ToString in Episode.
List<string> descriptions = new List<string>();
foreach (Episode episode in data.Episodes)
{
descriptions.Add(episode.Description);
}
listBox1.DataSource = descriptions;
More info on XML Serialization here:
http://msdn.microsoft.com/en-us/library/58a18dwa(v=vs.110).aspx

Get a single element of CSV file

I'm trying to add some csv elements to a list of Alimento, where Alimento is declared as:
namespace ContaCarboidrati
{
class Alimento
{
public virtual string Codice { get; set; }
public virtual string Descrizione { get; set; }
public virtual int Carboidrati { get; set; }
}
}
My csv looks something like this:
"C00, Pasta, 75".
Here's the method that should create the list from the csv:
private static List<Alimento> CreaListaAlimentiDaCsv()
{
List<Alimento> listaCsv = new List<Alimento>();
StreamReader sr = new StreamReader(#"C:\Users\Alex\Documents\RecordAlimenti.csv");
string abc = sr.ReadLine();
//listaCsv = abc.Split(",");
}
abc is "C00, Pasta, 75". I want to get a single element to add it to the list, or add all the 3 elements to the list, i thought that a single element is easier to made.
Sorry for my bad English
Thanks in advance
Alex
You are on the right track, but you cannot just create an Alimento of three strings, which is what you will get if you do abc.Split(","). You need to create a new Alimento object for each item (line) in the csv file and initialize each object correctly. Something like this:
var item = abc.Split(',');
listaCsv.Add(new Alimento() { Codice = item[0], Descrizione = item[1],
Carboidrati = int.Parse(item[2])};
Also, your csv seems to include spaces after the commas which you might want to get rid of. You could use string.Trim() to get rid of leading/trailing spaces. You also have to make sure the third item is actually an integer and take action if that is not the case (i.e. add some error handling).
As a side note, implementing a csv reader is not as trivial as one may think, but there are several free C# implementations out there. If you need something a bit more advanced than just reading a simple (and strictly one-line-per-item) csv, try one of these:
http://www.codeproject.com/Articles/9258/A-Fast-CSV-Reader
http://www.filehelpers.com/
You can parse file with LINQ
var listaCsv = (from line in File.ReadAllLines("RecordAlimenti.csv")
let items = line.Split(',')
select new Alimento {
Codice = items[0],
Descrizione = items[1],
Carboidrati = Int32.Parse(items[2])
}).ToList();
You can parse it pretty easy assuming your data isn't bad.
private IEnumerable<Alimento> CreaListaAlimentiDaCsv(string fileName)
{
return File.Readlines(fileName) //#"C:\Users\Alex\Documents\RecordAlimenti.csv"
.Select(line => line.Split(',').Trim())
.Select(
values =>
new Alimento
{
Codice = value[0],
Descrizione = values[0],
Carboidrati = Convert.ToInt32(values[3])
});
}
You can also use Linq on the method such as
//Takes one line without iterating the entire file
CreaListaAlimentiDaCsv(#"C:\Users\Alex\Documents\RecordAlimenti.csv").Take(1);
//Skips the first line and takes the second line reading two lines total
CreaListaAlimentiDaCsv(#"C:\Users\Alex\Documents\RecordAlimenti.csv").Skip(1).Take(1);

FileHelpers: FieldNotInFile attribute completely omits field from ReadFileAsDT DataTable results

I'm running into what appears to be unexpected behavior when utilizing the FieldNotInFile attribute in my mapping file. Please see below, abbreviated examples of what I have configured.
The mapping for the header record is defined separately to keep the option open for MasterDetail engine:
public class HeaderMapping
{
public string ID;
public DateTime RptDateFrom;
public DateTime RptDateTo;
public DateTime GenerationDate;
....
}
I would like to combine the values retrieved from the header into the final record result so they are specified with the FieldNotInFile attribute to be added later.
public class RecordMapping
{
// Values not in source
[FieldNotInFile()]
public string ID;
[FieldNotInFile()]
public DateTime RptDateFrom;
[FieldNotInFile()]
public DateTime RptDateTo;
[FieldNotInFile()]
public DateTime GenerationDate;
// Start values from source
public string RowPrefix;
public string Field1;
public string Field2;
public string Field3;
....
}
In the engine execution I have defined two instances. The first to capture the single header record and parse out its values. The AfterReadRecord event is used to stop the engine after the first line.
static void Main(string[] args)
{
// Extract the header
FileHelperEngine<HeaderMapping> headerEngine = new FileHelperEngine<HeaderMapping>();
headerEngine.AfterReadRecord +=
new FileHelpers.Events.AfterReadHandler<HeaderMapping>(AfterHeaderRead);
HeaderMapping[] headerRecord = headerEngine.ReadFile(source.FullName);
// Capture Values
companyId = headerRecord[0].ID;
rptDateFrom = headerRecord[0].RptDateFrom;
rptDateTo = headerRecord[0].RptDateTo;
generationDate = headerRecord[0].GenerationDate;
....
Next the record engine is created. The BeforeReadRecord event is used to insert the previously captured values into the placeholders signified in the RecordMapping with FieldNotInFile attributes.
....
// Extract the Records
FileHelperEngine<RecordMapping> recordEngine = new FileHelperEngine<RecordMapping>();
recordEngine.BeforeReadRecord +=
new FileHelpers.Events.BeforeReadHandler<RecordMapping>(BeforeThisRecord);
DataTable outputTable = recordEngine.ReadFileAsDT(source.FullName);
}
....
private static void BeforeThisRecord(EngineBase engine, BeforeReadEventArgs<RecordMapping> e)
{
e.Record.ID = companyId;
e.Record.RptDateFrom = rptDateFrom;
e.Record.RptDateTo = rptDateTo;
e.Record.GenerationDate = generationDate;
}
The outputTable result is not as expected. The fields marked as FieldNotInFile are completely omitted from the DataTable result. When debugging the process, the BeforeThisRecord method executes correctly and assigns the appropriate values but this is not reflected in the output. The DataTable columns are output as RowPrefix, Field1, Field2, etc. and not ID, RptDateFrom, RptDateTo, GenerationDate, RowPrefix, etc.
Strangely when I use the alternate method..
List <RecordMapping> recordList = recordEngine.ReadFileAsList(source.FullName);
The list items contain the RecordMapping objects with ALL of the correct values. It seems as though the DataTable translation of FieldNotInFile attributes is the culprit. Am I doing this wrong? Is this a bug?
You are correct that ReadFileAsDT() does not include the FieldNotInFile fields in the DataTable. It might be a bug, but honestly, I'm not sure how FieldNotInFile is supposed to be used - it's not in the documentation here.
I think you're better off using the Master Detail engine or alternatively just doing
RecordMapping[] recordMappings = recordEngine.ReadFile(source.FullName);
and then if you really need a DataTable, populate it yourself with something like:
DataTable outputTable = new DataTable(); // New data table.
outputTable.Columns.Add("ID", typeof(int)); // Add all columns.
outputTable.Columns.Add("RptDateFrom", typeof(DateTime));
outputTable.Columns.Add("RptDateTo", typeof(DateTime));
outputTable.Columns.Add("GenerationDate", typeof(DateTime));
outputTable.Columns.Add("RowPrefix", typeof(String));
outputTable.Columns.Add("Field1", typeof(String));
outputTable.Columns.Add("Field2", typeof(String));
outputTable.Columns.Add("Field3", typeof(String));
foreach (RecordMapping recordMapping in recordMappings)
{
outputTable.Rows.Add(
companyId,
rptDateFrom,
rptDateTo,
generationDate,
recordMapping.RowPrefix,
recordMapping.Field1,
recordMapping.Field2,
recordMapping.Field3)
}

Is LINQ to XML order dependent and can you number results with it?

So, my first question, is LINQ to XML order dependent?
Given the XML (simplified version of the actual file):
<root>
<dl Id='111' Or='true' />
<dl Id='112' Or='false' />
</root>
The container:
public class root
{
public int Position { get; set; }
public int Id { get; set; }
public bool Or { get; set; }
}
The reader code:
var t = a.DescendantsAndSelf("root").Descendants("dl").Select(b => new root
{
Id = int.Parse(b.Attribute("Id").Value),
Or = bool.Parse(b.Attribute("Or").Value)
});
Will the root elements in t always be in the order they were read out of the file? If not, is there any way to enforce ordering?
The second question, is there any way to automatically add a number to the reader code that says whether it was the first, second, third, etc. root to be read out of the XML? So in this example have it so t would contain two elements with the values
dl.Position = 1, dl.Id = 111, dl.Or = true
dl.Position = 2, dl.Id = 112, dl.Or = false
Yes, LINQ is "order dependent". Your objects get created in the order in which they appear in the XML file.
To get the position, use the version of Select which includes an index:
var t = a.DescendantsAndSelf("root").Descendants("dl").Select((b,idx) => new root
{
Id = int.Parse(b.Attribute("Id").Value),
Or = bool.Parse(b.Attribute("Or").Value),
Position = idx+1
});

Categories

Resources