0

I need some help with XDocument. I have file:

<?xml version="1.0" encoding="utf-8" ?>
<SaleGroupsFiles>
  <SaleGroup id="1" active="true" name="US"> 
   <files
      location="c:\mypath\"
      fileType="pdf"
      destination="outputpath">
    </files>
  </SaleGroup>

  <SaleGroup id="2" active="true" name="Canada"> 
   <files
      location="c:\mypath\"
      fileType="pdf"
      destination="outputpath">
    </files>
  </SaleGroup>
</SaleGroups>

I am trying to read the file using XDocument

static Dictionary<string, string> GetSettings(string path)
    {
        var document = XDocument.Load(path);
        var root = document.Root;
        var results =
          root
            .Elements()
            .ToDictionary(element => element.Name.ToString(), element => element.Value);
        return results;

    }

getting an error "element with that key already exists in dictionary". My guess it happens because "SaleGroup" is repeated more than once.

What is the proper way to read the data?

anjulis
  • 219
  • 1
  • 4
  • 14

1 Answers1

1

Your supposition is correct; as @maccettura explained, that's how Dictionary works.

This said, remeber that <SaleGroupsFiles> should have a matching tag for your XML to be valid.

As per your question, this is a possible idea:

var results =
    root
        .Descendants("SaleGroup")
        .ToDictionary(element => element.Attribute("id").Value.ToString(), element => element.ToString());

I.e. adding as keys the value of the id attribute of your elements, supposing they're unique.

Actually this question

What is the proper way to read the data?

is hard to answer to, since... it depends on what you want to do with the data. Do you want to create SaleGroup objects? In that case, rather than creating a dictionary, you can just create a List<SaleGroup>, if you provide a SaleGroup class.

Another option would be deserializing your XML.

Also, keep in mind that element.Name will give the element's name (e.g. SaleGroup), while maybe you want to read the value of the name attribute (e.g. "Canada")?


EDIT

Since you are ok with a slightly more realistic solution, if you declare these classes

public class SaleGroup
{
    public int Id { get; set; }
    public bool Active { get; set; }
    public string Name { get; set; }
    public SaleFile File { get; set; }
}

public class SaleFile
{
    public string Location { get; set; }
    public string FileType { get; set; }
    public string Destination { get; set; }
}

you can edit your code this way:

var results =
          root
            .Descendants("SaleGroup")
            .Select(element => new SaleGroup()
            {
                Active = Convert.ToBoolean(element.Attribute("active").Value),
                Id = Convert.ToInt32(element.Attribute("id").Value),
                Name = element.Attribute("name").Value,
                File = new SaleFile()
                {
                    Destination = element.Descendants("files").Single().Attribute("destination").Value,
                    FileType = element.Descendants("files").Single().Attribute("fileType").Value,
                    Location = element.Descendants("files").Single().Attribute("location").Value
                }
            })
            .ToList();

It is also possible to optimize the code above, e.g. to read element.Descendants("files").Single() just once, or modify it to allow more than one SaleFile inside your SaleGroup class.

Francesco B.
  • 2,729
  • 4
  • 25
  • 37
  • 1
    I just realized what the problem was too. Maybe point out that `.Name` is not the same as the _name_ attribute – maccettura May 09 '18 at 19:56
  • Francesco, thank you, The way you suggested I get everything written inside SaleGroup tag as a value in the dictionary. I can parse the string but I am sure there is a better solution. I really need to get an object for each SaleGroup with properties "name", "file location", "fileType", etc. So I create a SaleGroup class. What would be the syntax then to populate my List? To get slightly more complicated, there could be possibly more than one destination. I can have an array in my SaleGroup class but can I populate the List in one shot in this case? – anjulis May 09 '18 at 21:04
  • I mean that instead of destination I might have destination1, destination2, etc – anjulis May 09 '18 at 21:11
  • Francesco, thank you again. Just need to figure out how to handle variable number of destinations (destination1, destination2, ...) – anjulis May 09 '18 at 22:15
  • @anjulis it depends; one possibility is making the `File` property of `SaleGroup` class a list, like `public SaleFile List { get; set; }`; of course that implies that the multiple element is not just the `destination` attribute of `files` but `files` itself. Is this correct? – Francesco B. May 10 '18 at 08:37
  • 1
    @Francesco B no, the structure of the XML file is given. There is one "files" group, within it there is one Location, one FileType and variable number of destinations, listed as destination1, destination2... Perhaps, I need to use "Like" or regex to get all destinations but I cannot figure out how – anjulis May 10 '18 at 14:21
  • `element.Descendants("files").Single().Single().Attributes().Where(attribute => attribute.Name.LocalName.StartsWith("destination"))` might be a starting point – Francesco B. May 10 '18 at 14:37