views:

278

answers:

3

I'm writing a program in C# that will go through a bunch of config.xml files and update certain elements, or add them if they don't exist. I have the portion down that updates an element if it exists with this code:

XDocument xdoc = XDocument.Parse(ReadFile(_file));
XElement element = xdoc.Elements("project").Elements("logRotator")
                        .Elements("daysToKeep").Single();
element.Value = _DoRevert;

But I'm running into issues when I want to add an element that doesn't exist. Most of the time part of the tree is in place and when I use my code it adds another identical tree, and that causes the program reading the xml to blow up.

here is how I am attempting to do it

xdoc.Element("project").Add(new XElement("logRotator", new XElement("daysToKeep", _day)));

and that results in a structure like this(The numToKeep tag was already there):

<project>
  <logRotator>
    <daysToKeep>10</daysToKeep>
  </logRotator>
  <logRotator>
    <numToKeep>13</numToKeep>
  </logRotator>
</project>

but this is what I want

<project>
  <logRotator>
    <daysToKeep>10</daysToKeep>
    <numToKeep>13</numToKeep>
  </logRotator>
</project>
+1  A: 

If numToKeep already exists, try this:

xdoc.Element("project")
    .Element("logRotator")
    .Add(new XElement("daysToKeep", _day));

Otherwise, to add the whole line when logRotator doesn't exist, use:

xdoc.Element("project").Add(
    new XElement("logRotator", 
        new XElement("daysToKeep", _day),
        new XElement("numToKeep", _num)
));

See XElement.Add(Object[])

Codesleuth
That's what I thought, but some times the tree is deeper and sometimes it can be "cut off" at different lengths and I would just assume not write a case for each possibility.
Andy
@Andy: Can you add an xpath query to get all of your logRotator objects at once... then check if they have the daysToKeep and numToKeep children... if not... add them. If so... move onto the next logRotator.
Scott
@Scott: His (and our) code makes use of the `.Element()` method which gets the first element, so we can safely assume it's for just a single element.
Codesleuth
+2  A: 

This finds the logRotator element for a given project if it exists and creates the element if it does not.

// project is XElement
XElement logRotator = project.Element("logRotator");

if (logRotator == null)
{
    logRotator = new XElement("logRotator");
    project.Add(logRotator);
}

logRotator.Add(new XElement("daysToKeep", someValue));

For reusability (if you have other elements that could or could not exist), you could extract that into a method.

static void AddToElement(XElement outerParent, string innerParent, string name, object value)
{
    XElement inner = outerParent.Element(innerParent);
    if (inner == null)
    {
        inner = new XElement(innerParent);
        outerParent.Add(inner);
    }

    inner.Add(new XElement(name, value));
}

Simple test

string xml = @"<project> 
                    <logRotator> 
                    <numToKeep>13</numToKeep> 
                    </logRotator> 
                </project>";

XDocument document = XDocument.Parse(xml);
XElement project = document.Element("project");
AddToElement(project, "logRotator", "daysToKeep", 10);

Console.WriteLine(document.ToString());
Anthony Pegram
This works for ones that aren't deeper than two levels into the tree. I'm a little hazy on what's happening, but I'm attempting to see if I can adapt this to work with elements that are n levels deep. Thanks for the helps so far
Andy
A: 

With the kickstart from everyone here I ended up doing this and it works. It is far from elegant, but it gets the job done

/// <summary>
/// adds an element to an xml tree and builds the tree up if it is needed
/// </summary>
/// <param name="outerParent">root node</param>
/// <param name="innerPath">root/childOne/innerParent/name</param>
/// <param name="name">element to add</param>
/// <param name="value">elements value</param>
static XElement BuildTree(XElement outerParent, string innerParentPath, string name, object value)
{
    List<string> s = innerParentPath.Split('/').ToList(); 
    string str = "";
    XElement prevInner = null;
    if (s.Count != 2)//use 2 since we know the root will always be there
    {
        var t = new List<string>(s);
        t.RemoveRange(s.Count - 1, 1);//remove last element
        string[] sa = t.ToArray();
        str = string.Join("/", sa);
        prevInner = BuildTree(outerParent, str, name, value);//call recursively till we get to root;
    }
    else
    {
        prevInner = outerParent;
    }
    XElement inner = prevInner.Element(s[s.Count - 1]);
    if (inner == null)
    {
        if (s[s.Count - 1] == name)//add actual element if we're at top lvl.
        {
            prevInner.Add(new XElement(name, value));
        }
        else
        {
            inner = new XElement(s[s.Count - 1]);
            prevInner.Add(inner);
        }
    }
    return inner;
}
Andy