Preventing duplicate element access when reading an XML using LINQ - c#

I got an XML that I'm trying to parse using LINQ to XML and convert it to an anonymous list of objects. To do so, I come up with the following code snippet:
var res = doc
.Root
.Elements("Record")
.Elements("Term")
.Select(term => new
{
LanguageCode = term.Attribute("languageCode").Value,
ConceptNumber = Convert.ToInt32(term.Attribute("conceptNumber").Value),
IsHidden = Convert.ToBoolean(term.Attribute("hidden").Value),
Label = term.Value,
InputDate = DateTime.Parse(term.Parent.Element("InputDate").Value),
LastUpdate = DateTime.Parse(term.Parent.Element("LastUpdated").Value)
}).ToList();
Please pay attention to the InputDate & LastUpdate section. As you see, I've to access the parent node (say, term.Parent) so that I can access those 2 elements and this looks messy to me. Is there any way to declare term.Parent once and using it over and over again to extract those properties?
Here's an excerpt of the XML I'm trying to read:
<Record>
<Term languageCode="Prs" conceptNumber="10" hidden="False">Whatever</Term>
<Status>Approved</Status>
<Frequency>0</Frequency>
<InputDate>12/30/1899</InputDate>
<LastUpdate>10/25/2009</LastUpdate>
</Record>
Thank you

You need to use let clause. It creates a new variable inside query and allows to use it multiply times.
In your case it would be
var res = (from term in doc.Root.Elements("Record").Elements("Term")
let parent = term.Parent
select new
{
LanguageCode = term.Attribute("languageCode").Value,
ConceptNumber = Convert.ToInt32(term.Attribute("conceptNumber").Value),
IsHidden = Convert.ToBoolean(term.Attribute("hidden").Value),
Label = term.Value,
InputDate = parent.Element("InputDate").Value,
LastUpdate = parent.Element("LastUpdated").Value
}).ToList();
Note that its a code on a pure LINQ syntax that allow you to express ideas much clearer than using extension methods like in your question.

You can introduce an anonymous class in a projection (This is similar to what let does under the hood) and then use SelectMany() to flatten using the extra properties:
var results = doc.Elements("Record")
.Select( x => new
{
Terms = x.Elements("Term"),
InputDate = DateTime.Parse(x.Element("InputDate").Value),
LastUpdate = DateTime.Parse(x.Element("LastUpdate").Value)
})
.SelectMany(x => x.Terms, (record,term) => new
{
LanguageCode = term.Attribute("languageCode").Value,
..
InputDate = record.InputDate,
LastUpdate = record.LastUpdate
});

Related

Check if XML node value already exists in xml file using c#

Please note that I'm new to C# and I learn it right now :) I couldn't find something similar to my problem, so I came here.
I have an application in which I add customers (it's in the final stage). All customers are stored in an XML file. Every single customer gets a new customer number. In my xml file I got an XmlNode called CustNo. Now if the user add a new customer and type in a number which already exist, it should pop up a message box to say that this number already exists. I got this c# code:
XDocument xdoc = XDocument.Load(path + "\\save.xml");
var xmlNodeExist = String.Format("Buchhaltung/Customers/CustNo");
var CustNoExist = xdoc.XPathSelectElement(xmlNodeExist);
if (CustNoExist != null)
{
MessageBox.Show("asdf");
}
And my XML file looks like this:
<Buchhaltung>
<Customers>
<CustNo>12</CustNo>
<Surname>Random</Surname>
<Forename>Name</Forename>
<Addr>Address</Addr>
<Zip>12345</Zip>
<Place>New York</Place>
<Phone>1234567890</Phone>
<Mail>example#test.com</Mail>
</Customers>
<Customers>
<CustNo>13</CustNo>
<Surname>Other</Surname>
<Forename>Forename</Forename>
<Addr>My Address</Addr>
<Zip>67890</Zip>
<Place>Manhattan</Place>
<Phone>0987654321</Phone>
<Mail>test#example.com</Mail>
</Customers>
</Buchhaltung>
But then the message box always pops up. What am I doing wrong?
That's because your XPath return all CustNo elements, no matter of it's content.
Try following:
var myNumber = 12;
var xmlNodeExist = String.Format("Buchhaltung/Customers/CustNo[. = {0}]", myNumber.ToString());
or using First and LINQ to XML:
var myNumber = 12;
var xmlNodeExist = "Buchhaltung/Customers/CustNo";
var CustNoExist = xdoc.XPathSelectElements(xmlNodeExist).FirstOrDefault(x => (int)x == myNumber);
You are currently testing for existance of any 'CustNo' element. See this reference about the XPath syntax.
Your XPath should say something like this:
Buchhaltung//Customers[CustNo='12']
which would say "any customers element containing a 'CustNo' element with value = '12'"
Combining that with your current code:
var custNoGivenByCustomer = "12";
var xmlNodeExistsXpath = String.Format("Buchhaltung//Customers[CustNo='{0}']", custNoGivenByCustomer );
var CustNoExist = xdoc.XPathSelectElement(xmlNodeExistsXpath);
You can use LINQ to XML
var number = textBox1.Text;
var CustNoExist = xdoc.Descendants("CustNo").Any(x => (string)x == number);
if(CustNoExist)
{
MessageBox.Show("asdf");
}
This is because you select the CustNo elements regardless of their value. This will filter it to the desired customer number:
int custNo = 12;
var xmlNodeExist = String.Format("Buchhaltung/Customers[CustNo={0}]", custNo);
It selects the Customers elements instead, but since you're just checking for existence, that's unimportant.
W3Schools has a good tutorial/reference on XPath.

XDocument parsing

i have created a custom XDocument in c# and it looks like the following
<Filters datetimegenerated="28.07.2013 23:12PM">
<SimpleStringFilter column="xxx" table="yyy" groupby="True" seperatereport="true">
good,bad,ugly
</SimpleStringFilter>
<NumaricalFilter column="zzz" table = "mmm">zzz = 100 or zzz= 50</NumaricalFilter>
</Filters>
parsing it with in c# doesn't seem to work here is my code when i try to parse the StringFilterTags, however i get zero count from the above sample
var filters = from simplestringfilter in xdoc.Root.Element("Filters").Elements("SimpleStringFilter")
let column = simplestringfilter.Attribute("column")
let table = simplestringfilter.Attribute("table")
let groupby = simplestringfilter.Attribute("groupby")
let seperatecolumnby = simplestringfilter.Attribute("seperatereport")
let filterstringval = simplestringfilter.Value
select new
{
Column = column,
Table = table,
GroupBy = groupby,
SeperateColumnBy = seperatecolumnby,
Filterstring = filterstringval
};
what am i doing wrong?
Your query is searching off of the root element checking to see if it has a child Filters element. Since the root is the Filters element, that obviously fails which is why you are not getting any results.
There are two ways to resolve this problem. Just don't search for the Filters off of the root and your query should be fine.
var filters =
from simplestringfilter in xdoc.Root.Elements("SimpleStringFilter")
...
A better way to write it IMHO would be to not query off of the root but the document itself. It will look more natural.
var filters =
from simplestringfilter in xdoc.Element("Filters")
.Elements("SimpleStringFilter")
...

All nodes in XML using Linq C# [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Use LINQ to read all nodes from XML
I am trying to read an XML file using Linq in C# windows application. The sample of the xml string is given below.
<Root>
<Name>John Doe</Name>
<Data>FBCCF14D504B7B2DBCB5A5BDA75BD93B</Data>
<customer>true</customer>
<Accounts>1</Accounts>
<dataSet>
<Type1>Found matching records.</Type1>
<Type2>No matches found.</Type2>
<Type3>Found matching records.</Type3>
</dataSet>
</Root>
I want to display all the data inside the <dataset> tag and <datatag> i want to read <customer> tag as well.
I have created a class with members (string type, string status). Where in type i want to store the type1, 2...and in status i want to store what is inside the type node.
I am able to accomplish this but in the code i have to give
type1 = (string)row.Element("type1"),
type2=(string)row.Element("type2"),
i want to have a generic code in which i dont have to mention every type. In other words i want to read all the child nodes of tag whithout mentioning the tag name. I have spent 2 hours searching for this on google, but haven't found anything yet.
Expected output
save the information in class object (type and status).
And i want to read the customer tag so that i can know whether the person is already a customer
Any help will be very much appreciated.
Thanks
Update
According to inputs received from Raphaƫl Althaus
I have the following code:
var list = xml.Descendants("dataSet").Elements()
.Select(m => new CustomerInfo
{
Type = m.Name.LocalName,
Value = m.Value
}).ToList();
foreach (CustomerInfo item in list)
{
MessageBox.Show(item.Type+ " "+item.Value);
}
and for reading the Customer tag i have written more code.
var isCustomer = from customer in xmlDoc.Descendants("Root")
select new
{
customer = tutorial.Element("customer").Value,
}
Can i do both in one query?. Or this method is not so heavy on performance, so i can use this?
something like that ?
var q = xml.Descendants("dataSet").Elements()
.Select(m => new
{
type = m.Name.LocalName,
value = m.Value
}).ToList();
You can also directly populate a list of your "class with members"
var list = xml.Descendants("dataSet").Elements()
.Select(m => new <TheNameOfYourClass>
{
Type = m.Name.LocalName,
Value = m.Value
}).ToList();
EDIT :
to get the "customer" value, I would do another query
var customerElement = xml.Element("customer");
var isCustomer = customerElement != null && customerElement.Value == "true";
So you could mix all of that it in a little function
public IList<YourClass> ParseCustomers(string xmlPath, out isCustomer) {
var xml = XElement.Load(xmlPath);
var customerElement = xml.Element("customer");
isCustomer = customerElement != null && customerElement.Value == "true";
return xml.Descendants("dataSet").Elements()
.Select(m => new <YourClass>
{
Type = m.Name.LocalName,
Value = m.Value
}).ToList();
}

Altering the results of collection before binding it to a listbox

I have a simple Linq to XML query that i run and the results are bound to a listbox (via a collection). What i can't figure out is how to alter the result before binding them, for example. I have the following code:
XElement xmlEvents = XElement.Parse(e.Result);
lstb.ItemsSource = from GetEvents in xmlEvents.Descendants("e2event")
select new GetEvents
{
eventid = GetEvents.Element("eventid").Value,
eventtime = GetEvents.Element("eventtime").Value
};
eventtime returns a linux time stamp, i have a little method that converts this into a datetime stamp. So how would i go about converting to this before binding to the listbox?
Well the simplest approach would be to change your projection:
XElement xmlEvents = XElement.Parse(e.Result);
lstb.ItemsSource =
from GetEvents in xmlEvents.Descendants("e2event")
select new GetEvents
{
eventid = GetEvents.Element("eventid").Value,
eventtime = ConvertTime(GetEvents.Element("eventtime").Value)
};
(Where ConvertTime is your method.)
Note that if this is an integer value, you could get LINQ to XML to perform the numeric conversion for you:
XElement xmlEvents = XElement.Parse(e.Result);
lstb.ItemsSource =
from GetEvents in xmlEvents.Descendants("e2event")
select new GetEvents
{
eventid = GetEvents.Element("eventid").Value,
eventtime = ConvertTime((long) GetEvents.Element("eventtime"))
};

Improving Linq-XML to Object query

I want to use Linq to extract data from an XML document and place it into a list
<Data>
<FlightData DTS="20110216 17:17" flight="1234" origin="CYYZ" dest="CYUL" aircraft="945">
<TLDRequest>
<Airline>ABC</Airline>
<AcReg>C-FABC</AcReg>
<CalcType>T</CalcType>
<OAT>-05</OAT>
<Wind>060/10</Wind>
<Flaps>5</Flaps>
<Switches></Switches>
<Runways>
<Rwy>6L</Rwy>
<Rwy>6R</Rwy>
</Runways>
...
</TLDRequest>
...
</FlightData>
</Data>
My Linq code in C# works - I can get attributes from the FlightData tab, but I think it could be more efficient, especially in the area of getting data from the TLDRequest tag. Can I get some insight on using best practices to get to and grab child tags?
public static List<ACARS_Phase> createAcarsPhaseObject(XDocument xDoc)
{
return (from ao in xDoc.Descendants("FlightData")
select new ACARS_Phase
{
FlightDate = DateTime.ParseExact(ao.Attribute("DTS").Value, "yyyyMMdd HH:mm", new CultureInfo("en-CA")),
FlightNumber = ao.Attribute("flight").Value,
Origin = ao.Attribute("origin").Value,
Destination = ao.Attribute("dest").Value,
InternalFinNumber = ao.Attribute("aircraft").Value,
OperatorCode = ao.Element("TLDRequest").Element("Airline").Value,
RegistrationNumber = ao.Element("TLDRequest").Element("AcReg").Value,
Wind = ao.Element("TLDRequest").Element("Wind").Value,
Flaps = ao.Element("TLDRequest").Element("Flaps").Value,
OAT = ao.Element("TLDRequest").Element("OAT").Value,
}).ToList();
}
Best regards
Your query is fine, generally speaking. If you want to cut down on some of the redundancy, consider using let to get the TLDRequest element once, so you repeat yourself a bit less.
return (from ao in xDoc.Descendants("FlightData")
let request = ao.Element("TLDRequest")
select new AcARS_Phase
{
// stuff
OperatorCode = request.Element("Airline").Value,
RegistrationNumber = request.Element("AcReg").Value,
// etc.
}).ToList();

Categories

Resources