3
\$\begingroup\$

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?

t3chb0t
44.6k9 gold badges84 silver badges190 bronze badges
asked Oct 10, 2018 at 15:58
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

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.

answered Oct 10, 2018 at 18:00
\$\endgroup\$
1
\$\begingroup\$

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();
 }
answered Oct 10, 2018 at 18:29
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.