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?

\$\endgroup\$
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.

\$\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();
  }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.