Search code examples
c#xmldatagridviewnodes

C# : select multiple nodes from XML string


With the code below I am trying to show XML nodes in a DataGridView. The first row should contain red and blue, the second row should contain green and yellow.

string xml = "<?xml version="1.0" encoding="utf-8"?>
<colors>
<color type="string">red</color>
<color type="string">blue</color>
</colors>
<colors>
<color type="string">green</color>
<color type="string">yellow</color>
</colors>
";

StringReader reader = new StringReader(xml);
XDocument doc = XDocument.Load(reader);

var res = doc.Descendants("colors").Select(n => new { n.Element("color").Value }).ToList());

dataGridView.DataSource = res;

It only shows the first value:

| red |
| green |

How do I select both color values as a result for the datagridview:

Result

| red | blue |
| green | yellow |

Solution

  • If you want the result the way you show it, you'll have to do a bit more work, just having the linq expression work isn't enough

    First. change your link query like this:

    var result = doc.Descendants("colors")
                    .SelectMany((x) => x.Elements("color").Select((c, i) => new { Id = i, Color = c.Value }));
    

    We add an index to our result class so we can use it later for mapping the color to their own column.

    Next you'll want to transform your data otherwise all colors will go in one column, as each property is mapped to a column:

    I've added a few comments in the below code that explains what it does.

    
    // create a new DataTable for mapping the transformed data
    var table = new DataTable();
    
    // loop through all items to create a column for each index.
    foreach (var item in result)
    {
        var colName = item.Id.ToString();
        if (!table.Columns.Contains(colName))
        {
            table.Columns.Add(colName);
        }
    }
    
    // loop again through the results to add each item to the right position in the datatable
    foreach (var item in result)
    {
        var isMapped = false;
    
    // a second foreach to check if it should go into an existing row
        foreach (DataRow dataRow in table.Rows)
        {
            if (dataRow[item.Id.ToString()].ToString() == "")
            {
                dataRow[item.Id.ToString()] = item.Color;
                isMapped = true; // set to true so we don't map it again later
                break; // no need to continue the loop, we found where the color belongs
            }
        }
        if (!isMapped)
        {
            var row = table.NewRow(); // it doesn't belong in an existing row
            row[item.Id.ToString()] = item.Color;
            table.Rows.Add(row);
        }
    }
    
    // Assign the new table to your view
    dataGridView1.DataSource = table;
    

    That should do it. Keep in mind that for large sets of data these loops could become slow. There is probably a more optimal solution, but it get's the job done, you should now have data that looks like this:

    | red | blue |
    | green | yellow |