cyberbemon
cyberbemon

Reputation: 3190

Removing a specific entry from an XML file

I have the following xml file

- <annotations>
  - <annotation value="Black and white, outdoor" eventID="1">
          <image location="0928262.JPG" /> 
    </annotation>

  - <annotation value="CLear, obstacles" eventID="2">
          <image location="0928262.JPG" /> 
    </annotation>
  </annotations>

I have a function that takes in the file location and then deletes that from the xml file, like so,

private void remEntry(String fName)
{
    string xmlFile = path + "\\output.xml";

    if (File.Exists(xmlFile))
    {
        XDocument xdoc = XDocument.Load(xmlFile);

        foreach (var x in xdoc.Descendants("annotation").ToList())
        {
            foreach (var el in xdoc.Descendants("image").ToList())
            {
                if (el.Attribute("location").Value == fName)
                {
                    el.Remove();

                }
            }
        }
        xdoc.Save(xmlFile);
    }
}

And this works, but I have a problem, this code will remove all the instance of the file name from the xml file, I only want to remove filename tied to a specific eventID. The event ID is displayed on the application in a text window. So, I added the following to the loop

foreach (var x in xdoc.Descendants("annotation").ToList())
{
    foreach (var el in xdoc.Descendants("image").ToList())
    {
        if (el.Attribute("location").Value == fName &&        
            x.Attribute("eventID").Value == eventID.Text)
        {
            el.Remove();

        }
    }

The idea here is, if the image name is equal to the input and if the eventID of the image is same as the EventID displayed then remove the element. But it still removes all instance of the file name.

Here is the expected result, where the eventID = 2.

- <annotations>
      - <annotation value="Black and white, outdoor
              <image location="0928262.JPG" /> 
        </annotation>

      - <annotation value="CLear, obstacles" eventID="2">

        </annotation>
      </annotations>

Here is what I'm getting,

- <annotations>
      - <annotation value="Black and white, outdoor" eventID="1">

        </annotation>

      - <annotation value="CLear, obstacles" eventID="2">

        </annotation>
      </annotations>

Upvotes: 2

Views: 99

Answers (4)

Thijs
Thijs

Reputation: 283

.It seems that the nesting of your loops is incorrect. Initially you loop over all annotations in the document: foreach (var x in xdoc.Descendants("annotation").ToList()) and the you loop over all images in the document: foreach (var el in xdoc.Descendants("image").ToList())

If you would print the location and eventID's you would get this:

  • eventID 1, 0928262.JPG (first location)
  • eventID 1, 0928262.JPG (second location)
  • eventID 2, 0928262.JPG (first location)
  • eventID 2, 0928262.JPG (second location)

As you can see from this output you will match your criteria for removing the entry twice.

What you probably should do is something like:

foreach (var x in xdoc.Descendants("annotation").ToList())
{
    foreach (var el in x.Descendants("image").ToList())
    {
        if (el.Attribute("location").Value == fName &&
            x.Attribute("eventID").Value == eventID.Text)
        {
            el.Remove();
        }
    }
}

Upvotes: 1

AkshayP
AkshayP

Reputation: 188

I think in inner foreach loop you are having xdoc.descendents(), which is giving you both image elements. Try to use x.Descendents()-

foreach (var el in x.Descendants("image").ToList())

Upvotes: 1

Michał Komorowski
Michał Komorowski

Reputation: 6238

Instead of using embedded foreach loops to find a node to be removed you can also use XPath query. To do so, firstly you have to import System.Xml.XPath namespace:

using System.Xml.XPath;

Then use the following code:

var id = 1;
var fileName = "0928262.JPG";
foreach (var x in xdoc.XPathSelectElements(String.Format(@"//annotation[@eventID={0}]/image[@location=""{1}""]", id, fileName)))
{
    x.Remove();
}

Upvotes: 2

Jehof
Jehof

Reputation: 35544

Change your method to the following and it will work

foreach (var x in xdoc.Descendants("annotation").ToList())
{
  foreach (var el in x.Descendants("image").ToList())
  {
    if (el.Attribute("location").Value == loc && x.Attribute("eventID").Value == id)
    {
      el.Remove();
    }
  }
}

The second foreach needs to get the Descendants of the current element that is provided by the first foreach.

Your code gets the Descendants always from the complete document, what results in that when the first foreach encounters the element with eventID == 2 it will remove all images with the specified location from the complete document.

Upvotes: 3

Related Questions