Reputation: 1420
I am trying create a function that takes in x retention days, which as a result compares the "date" tags in my XML file, and deletes entries accordingly.
<?xml version="1.0" encoding="utf-8"?>
<root>
<OFBM time="13:17" date="06.10.2017" saveName="Unnamed save">
<folder name="file:///C:/Users/AD/Downloads" />
<folder name="file:///C:/Users/AD/Desktop/t" />
<folder name="file:///C:/Users/AD/Dropbox/Development/DelOldX/DelOldX" />
</OFBM>
<OFBM time="13:17" date="31.08.2017" saveName="Unnamed save">
<folder name="file:///C:/Users/AD/Downloads" />
<folder name="file:///C:/Users/AD/Desktop/t" />
<folder name="file:///C:/Users/AD/Dropbox/Development/DelOldX/DelOldX" />
</OFBM>
<OFBM time="13:17" date="31.08.2017" saveName="Unnamed save">
<folder name="file:///C:/Users/AD/Downloads" />
<folder name="file:///C:/Users/AD/Desktop/t" />
<folder name="file:///C:/Users/AD/Dropbox/Development/DelOldX/DelOldX" />
</OFBM>
</root>
For example my retentionDays value is 6, today is 6th of October (06.10), so everything before 1st of October should be deleted. I wrote up a function that does this, however it deletes the date attribute, not the whole element
My function:
public void CleanXML()
{
int days = Int32.Parse(tbRetentionDays.Text);
DateTime minDate = DateTime.Now.AddDays(-days);
var root = XElement.Load(pathToXml);
foreach (XElement el in root.Elements("OFBM"))
{
foreach (XAttribute el2 in el.Attributes("date"))
{
string rawDate = el2.Value;
DateTime xmlDate = Convert.ToDateTime(rawDate);
if (xmlDate < minDate)
{
Console.WriteLine(xmlDate + " lower than " + minDate + " Retention: " + days);
el.Remove();
}
}
}
root.Save(pathToXml);
}
Upvotes: 1
Views: 189
Reputation: 42444
When you call el.Remove();
you're modifying the collection of elements you're iterating over. That causes that not all elements are visited and therefor removed. One approach could be to store the elements that needs to be deleted first and once you've completed that, remove each individual element.
Your code needs to be adapted like so:
public void CleanXML(string daysText)
{
int days = Int32.Parse(daysText);
DateTime minDate = DateTime.Now.AddDays(-days);
var root = XElement.Load(pathToXml);
// keep list of items to be removed
var remove = new List<XElement>();
foreach (XElement el in root.Elements("OFBM"))
{
foreach (XAttribute el2 in el.Attributes("date"))
{
string rawDate = el2.Value;
DateTime xmlDate = Convert.ToDateTime(rawDate);
if (xmlDate < minDate)
{
Console.WriteLine(xmlDate + " lower than " + minDate + " Retention: " + days);
// keep a reference to this element
remove.Add(el);
}
}
}
// remove individual elements
foreach(var element in remove)
{
element.Remove();
}
root.Save(pathToXml);
}
If you don't want to have that explicit list you can rewrite your Linq query a bit so you obtain the list of elements to be remove and materialize that list. The main iterator would look like this in that case:
foreach (XElement el in root
.Elements("OFBM")
.Where(elem => elem.Attribute("date") != null
&& Convert.ToDateTime(elem.Attribute("date").Value) < minDate
).ToList()) // the ToList is mandatory here
{
el.Remove();
}
Upvotes: 1
Reputation: 236228
You should check if days text can be parsed into integer
int retentionInDays;
if (!Int32.TryParse(daysText, out retentionInDays))
return; // log, throw etc
Thus you don't check time attribute in xml, then you can use DateTime.Today
. But I would recommend you to check both date and time in your xml file.
var minDate = DateTime.Today.AddDays(-retentionInDays);
var xdoc = XDocument.Load(pathToXml);
var provider = CultureInfo.InvariantCulture
Next you just select expired nodes and remove them all. As @rene noticed, if you remove node during enumeration in foreach, then enumeration finishes (though I wonder why we don't have something like CollectionModified exception here). Note that you should parse date manually because xml date string does not have default format - by default month goes before day and "06.10.2017"
will be converted to June 10th instead of October 6th. "31.08.2017"
will produce FormatException
. So, use ParseExact
here.
var expiredEntries =
from e in xdoc.Root.Elements("OFBM")
let date = DateTime.ParseExact((string)e.Attribute("date"), @"dd\.MM\.yyyy", provider)
where date < minDate
select e;
expiredEntries.Remove(); // removes all selected nodes
xdoc.Save(pathToXml);
If you need to log all nodes before removing them, you can enumerate expired entries.
Upvotes: 0
Reputation: 94
It looks like there is a localization issue where its reading the first number as month instead of day. Its reading 06.10.2017 as June 10th. Try using DateTime.ParseExact(rawDate, "dd.MM.yyyy") instead. That way, you won't have to worry about the region settings of the machine your code is running on.
Upvotes: 0