I'm pulling the values from the following XML based on the attribute name.
<DataRow>
<DataItem name="SYMBOL">MSFT-US</DataItem>
<DataItem name="NAME">Microsoft Corporation</DataItem>
<DataItem name="CUSIP">59491812311</DataItem>
<DataItem name="SEDOL">2588CFA</DataItem>
<DataItem name="CURRENCY">USD</DataItem>
<DataItem name="DATE">2018年10月05日</DataItem>
<DataItem name="PRICE">100.25</DataItem>
</DataRow>
Here's my working code to select the values into a list:
IEnumerable<XElement> xDoc = XDocument.Parse(xmlString).Descendants("DataRow");
var query = from item in xDoc
let symbol = (item.Elements("DataItem")
.Where(i => (string)i.Attribute("name") == "SYMBOL").Select(i => i.Value)).FirstOrDefault()
let compName = (item.Elements("DataItem")
.Where(i => (string)i.Attribute("name") == "NAME").Select(i => i.Value)).FirstOrDefault()
let cusip = (item.Elements("DataItem")
.Where(i => (string)i.Attribute("name") == "CUSIP").Select(i => i.Value)).FirstOrDefault()
let sedol = (item.Elements("DataItem")
.Where(i => (string)i.Attribute("name") == "SEDOL").Select(i => i.Value)).FirstOrDefault()
select new {
Symbol = symbol,
CompName = compName,
Cusip = cusip,
Sedol = sedol
};
I feel that this can be made cleaner. How can it be done?
2 Answers 2
If you don't want create a full-blown xml
serializer (I wouldn't if it was a simple as that) all you can do is to just tide this up. This is, add a helper variable for dataItems
and create a helper extension for finding elements based on the Name
attribute. Then you can remove all the other intermediate viariables and directly assign the values to the properties because this is pretty short right now.
public static string ValueWhereName(this IEnumerable<XElement> sournce, string name)
{
return sournce.SingleOrDefault(i => i.Attribute("name").Value.Equals(name))?.Value;
}
and use it like this:
var query =
from item in dataRows
let dataItems = item.Elements("DataItem")
select new
{
Symbol = dataItems.ValueWhereName("SYMBOL"),
CompName = dataItems.ValueWhereName("NAME"),
Cusip = dataItems.ValueWhereName("CUSIP"),
Sedol = dataItems.ValueWhereName("SEDOL")
};
If you had a dedicated class
for the result you could make it even more robust by using nameof(YourClass.Symbol)
. But then the properties would have to have the same names as the attributes or you would need to decorate them with your own attributes and use some reflection... You would also need to modify the Equals(name, StringComparison.OrdinalIgnoreCase)
call to ignore-case sind the properties obviously wouldn't match the xml
attributes casewise.
Another option could be the package Json.NET
as it's also able to parse xml
but I'm not sure how it could handle the attributes.
Alternatively you can create a dictionary:
var result = xDoc.Descendants("DataItem").ToDictionary(e => e.Attribute("name").Value, e => e.Value);
foreach (var key in result.Keys)
{
Console.WriteLine($"{key} = {result[key]}");
}
Or if you have more <DataRow>
siblings:
var result = xDocument.Descendants("DataRow").Select(r => r.Descendants("DataItem").ToDictionary(e => e.Attribute("name").Value, e => e.Value));
foreach (var dict in result)
{
foreach (var key in dict.Keys)
{
Console.WriteLine($"{key} = {dict[key]}");
}
Console.WriteLine();
}