向XDocument添加XElement时出现重复元素

3

我正在用C#写一个程序,该程序将浏览一堆config.xml文件,并更新某些元素,如果这些元素不存在,则添加它们。我已经编写了以下代码,可以更新已存在的元素:

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

但是当我想要添加一个不存在的元素时,我遇到了问题。大多数情况下,树的一部分已经存在,当我使用我的代码时,它会添加另一个相同的树,这会导致读取xml的程序崩溃。

以下是我的尝试方式:

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

结构如下所示(numToKeep 标记已经存在):
<project>
  <logRotator>
    <daysToKeep>10</daysToKeep>
  </logRotator>
  <logRotator>
    <numToKeep>13</numToKeep>
  </logRotator>
</project>

但这正是我想要的。

<project>
  <logRotator>
    <daysToKeep>10</daysToKeep>
    <numToKeep>13</numToKeep>
  </logRotator>
</project>
3个回答

2
如果存在,此功能将查找给定项目的logRotator元素,并创建该元素(如果不存在)。
// project is XElement
XElement logRotator = project.Element("logRotator");

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

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

为了提高代码的可重用性(如果您有其他可能存在或不存在的元素),您可以将其提取到一个方法中。

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));
}

简单测试

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());

这适用于树形结构深度不超过两层的情况。我对正在发生的事情有点模糊,但我正在尝试看看能否使其适用于深度为n级的元素。到目前为止,谢谢您的帮助。 - Andy

1
如果 numToKeep 已经存在,则尝试这样做:
xdoc.Element("project")
    .Element("logRotator")
    .Add(new XElement("daysToKeep", _day));

否则,当logRotator不存在时,可以使用以下方法添加整行:
xdoc.Element("project").Add(
    new XElement("logRotator", 
        new XElement("daysToKeep", _day),
        new XElement("numToKeep", _num)
));

请参见XElement.Add(Object[])


那正是我想的,但有时树的深度不同,有时可能会在不同的长度处被“截断”,我宁愿不为每种可能性编写一个情况。 - Andy
@Andy: 你能添加一个xpath查询以一次性获取所有的logRotator对象吗?然后检查它们是否有daysToKeep和numToKeep子项,如果没有,就添加。 如果有,就继续下一个logRotator。 - Scott
@Scott:他(以及我们)的代码使用了.Element()方法来获取第一个元素,因此我们可以安全地假设它只是针对单个元素。 - Codesleuth

0

通过大家的启发,我最终做到了这一点并且它可以工作。虽然远非优美,但确实能完成任务。

/// <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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接