如何更改新XML文档中属性的值?

时间:2015-01-23 20:25:58

标签: c# .net xml xmldocument

我有一个数据库,其中有大量的XML文件正在等待更新。更新将擦除数据库中的所有文件。除属性值外,每个新文件都与之前的文件相同。

但我想保留每个type属性session作为值。此属性在每个文件中多次出现。但是,它目前还不起作用:属性根本没有更新。

  

如何根据另一个文件更改相同的新XML文件中特定属性的值?

这是我到目前为止所做的......

所以我决定在擦除之前找到一种方法来为每个文件使用此代码获取这些属性的路径:

List<XmlList> dBPathTypeSession = new List<XmlList>();
/*reader*/
XmlDocument doc = new XmlDocument();
doc.LoadXml(sessionType.Parameters);

XmlNode root = doc.DocumentElement;

//I think my problem come from this line below
XmlNodeList nodes = root.SelectNodes("//node()[@type='session']");

dBPathTypeSession.Add(new XmlList("b_session_type", i, nodes));//table,row,paths
  

这一行下面的代码可能不需要回答我的问题,这只是更详细的信息。

然后使用以下命令更正更新后的属性值:

XmlDocument doc = new XmlDocument();
doc.LoadXml(sessionType.Parameters);

foreach (XmlNode node in file.Paths)//XmlList file = new XmlList();
{
    if (node.Attributes["type"].Value == "system")
    {
        node.Attributes["type"].Value = "session";
    }
}
//push to DB

每个文件中每个路径的信息都包含在:

//But I think this is pointless for my question
public class XmlList
{
    public XmlList(string tablePath, int filePath, XmlNodeList paths)
    {
        this.TablePath = tablePath;
        this.FilePath = filePath;
        this.Paths = paths;
    }
    public string TablePath {get; private set;}
    public int FilePath {get; private set;}
    public XmlNodeList Paths {get;set;}
}

我使用C#3.0(.NET framework 3.5),我必须使用XMLDocument使其适合代码中的其他所有内容。

以下是XML更新前的样子(简短版)

的示例
<Session is_hidden="false">
    <ID is_static="true">1</ID>
    <SESSIONIDX is_static="true">0</SESSIONIDX>
    <Timing>
        <FirstPresentation display_name="FirstPresentation" type="system"/>
        <Pause display_name="Pause" type="system" datatype="float"/>
        <Feedback display_name="Feedback" type="session" datatype="float"/>
        <Answer display_name="Answer" type="system" datatype="float"/>
    </Timing>
    <Balls>
        <Indexed display_name="Indexed" type="session" datatype="pos_int"/>
        <IndexingColor1 display_name="IndexingColor1" type="system" datatype="list">
            <list>
                <ListItem>RED</ListItem>
                <ListItem>BLUE</ListItem>
            </list>
        </IndexingColor1>
    </Balls>
</Session>

以下是XML更新后的样子(简短版)

的示例
<Session is_hidden="false">
    <ID is_static="true">1</ID>
    <SESSIONIDX is_static="true">0</SESSIONIDX>
    <Timing>
        <FirstPresentation display_name="FirstPresentation" type="session"/>//system change for session
        <Pause display_name="Pause" type="system" datatype="float"/>
        <Feedback display_name="Feedback" type="system" datatype="float"/>//session change for system
        <Answer display_name="Answer" type="system" datatype="float"/>//system stay system
    </Timing>
    <Balls>
        <Indexed display_name="Indexed" type="session" datatype="pos_int"/>//session stay session
        <IndexingColor1 display_name="IndexingColor1" type="system" datatype="list">
            <list>
                <ListItem>RED</ListItem>
                <ListItem>BLUE</ListItem>
            </list>
        </IndexingColor1>
    </Balls>
</Session>

以下是我正在寻找的XML内容的示例:

<Session is_hidden="false">
    <ID is_static="true">1</ID>
    <SESSIONIDX is_static="true">0</SESSIONIDX>
    <Timing>
        <FirstPresentation display_name="FirstPresentation" type="session"/>//system stay session
        <Pause display_name="Pause" type="system" datatype="float"/>
        <Feedback display_name="Feedback" type="session" datatype="float"/>//session return session
        <Answer display_name="Answer" type="system" datatype="float"/>//system stay system
    </Timing>
    <Balls>
        <Indexed display_name="Indexed" type="session" datatype="pos_int"/>//session stay session
        <IndexingColor1 display_name="IndexingColor1" type="system" datatype="list">
            <list>
                <ListItem>RED</ListItem>
                <ListItem>BLUE</ListItem>
            </list>
        </IndexingColor1>
    </Balls>
</Session>

如果我们将它与布尔代数进行比较,我们有:

  

对于x = session&amp; y =系统

     

更新前=更新后 - >我们想要什么

     
    

x = x - &gt; X

         

x = y - &gt; X

         

y = x - &gt; X

         

y = y - &gt; ÿ

  

如果您需要更多信息,请在评论中提问,我会更新帖子。

2 个答案:

答案 0 :(得分:1)

问题是您正在搜索名为type且值为session的属性的节点 - 然后在当前值为{{1}时替换该值}。这不起作用,因为价值不可能两者兼而有之。

你必须要么:

system

    foreach (XmlNode node in root.SelectNodes("//node()[@type='session']"))
        node.Attributes["type"].Value = "system";

<强>更新

如果有两个 foreach (XmlNode node in root.SelectNodes("//node()[@type='system']")) node.Attributes["type"].Value = "session"; 具有相同的元素层次结构但每个元素的属性集不同,并且希望将某些属性信息从第一个传播到第二个,则需要遍历元素层次结构并创建临时在它们之间映射表。下面这样做,假设元素按名称对应,然后按顺序存在重复名称(例如在列表中):

XmlDocuments

然后称之为:

    static void WalkMatchingElements(XmlElement root1, XmlElement root2, Action<XmlElement, XmlElement> action)
    {
        WalkMatchingElements(root1, root2, (element) => (element.Name), action);
    }

    static void WalkMatchingElements<TKey>(XmlElement root1, XmlElement root2, Func<XmlElement, TKey> getKey, Action<XmlElement, XmlElement> action)
    {
        if (EqualityComparer<TKey>.Default.Equals(getKey(root1), getKey(root2)))
            action(root1, root2);
        var children1GroupedByName = root1.ChildNodes.OfType<XmlElement>().GroupBy(getKey);
        var children2LookupByName = root2.ChildNodes.OfType<XmlElement>().ToLookup(getKey);
        foreach (var child1group in children1GroupedByName)
        {
            var child2group = children2LookupByName[child1group.Key];
            foreach (var pair in child1group.Zip(child2group, (el1, el2) => new KeyValuePair<XmlElement, XmlElement>(el1, el2)))
                WalkMatchingElements(pair.Key, pair.Value, getKey, action);
        }
    }

Update2 如果您不想同时在内存中存放整个 var oldDoc = new XmlDocument(); oldDoc.LoadXml(oldXml); var newDoc = new XmlDocument(); newDoc.LoadXml(newXml); WalkMatchingElements(oldDoc.DocumentElement, newDoc.DocumentElement, (elOld, elNew) => { var attrOld = elOld.Attributes["type"]; if (attrOld != null && attrOld.Value == "session") { elNew.SetAttribute("type", "system"); } }); (虽然我不知道为什么不这样做),您可以构建一个查找表具有XmlDocument属性的元素,由path索引,然后使用它:

type

您需要以下扩展方法:

    const string AttributeName = "type";

        var lookup = oldDoc.DocumentElement.DescendantsAndSelf().OfType<XmlElement>().Where(el => el.HasAttribute(AttributeName)).ToLookup(el => el.Path(), el => el.Attributes[AttributeName].Value);

        // And then later

        WalkMatchingElements(new XmlElement[] { newDoc.DocumentElement }, lookup, (el, oldValue) =>
            {
                if (oldValue != null && oldValue == "session")
                    el.SetAttribute(AttributeName, "session");
            });


    private static void WalkMatchingElements<TValue>(IEnumerable<XmlElement> elements, ILookup<string, TValue> pathLookup, Action<XmlElement, TValue> action)
    {
        var elementsByPath = elements.GroupBy(el => el.Path());
        foreach (var elementsGroup in elementsByPath)
        {
            foreach (var pair in elementsGroup.Zip(pathLookup[elementsGroup.Key], (el, value) => new KeyValuePair<XmlElement, TValue>(el, value)))
                action(pair.Key, pair.Value);
            foreach (var element in elementsGroup)
                WalkMatchingElements(element.ChildNodes.OfType<XmlElement>(), pathLookup, action);
        }
    }

(我忘记了public static class XmlNodeExtensions { public static string Path(this XmlElement element) { if (element == null) throw new ArgumentNullException(); return element.AncestorsAndSelf().OfType<XmlElement>().Reverse().Aggregate(new StringBuilder(), (sb, el) => sb.Append("/").Append(el.Name)).ToString(); } public static IEnumerable<XmlNode> AncestorsAndSelf(this XmlNode node) { for (; node != null; node = node.ParentNode) yield return node; } public static IEnumerable<XmlNode> DescendantsAndSelf(this XmlNode root) { if (root == null) yield break; yield return root; foreach (var child in root.ChildNodes.Cast<XmlNode>()) foreach (var subChild in child.DescendantsAndSelf()) yield return subChild; } } public static class EnumerableExtensions { // Back ported from .Net 4.0 public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) { if (first == null) throw new ArgumentNullException("first"); if (second == null) throw new ArgumentNullException("second"); if (resultSelector == null) throw new ArgumentNullException("resultSelector"); return ZipIterator(first, second, resultSelector); } static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>(IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) { using (IEnumerator<TFirst> e1 = first.GetEnumerator()) using (IEnumerator<TSecond> e2 = second.GetEnumerator()) while (e1.MoveNext() && e2.MoveNext()) yield return resultSelector(e1.Current, e2.Current); } } 不在.Net 3.5中。)

答案 1 :(得分:1)

问题中代码的问题是XmlNodeList:它创建了一个链接到XML文件的对象列表。因此,当文件被擦除时,XmlNodeList意味着什么。因此,将这个XmlNode列表更改为这些XPath的string列表都可以。

XmlNodeList nodes = root.SelectNodes("//node()[@type='session']");
List<string> xPathList = new List<string>();
foreach (XmlNode node in nodes)
{
    xPathList.Add(getXPath(node));
}

dBPathTypeSession.Add(new XmlList("b_session_type", i, xPathList));//table,row,paths

getXPath函数是这样的:

static public string getXPath(XmlNode _xmlNode)
{
    Stack<string> xpath = new Stack<string>();

    while (_xmlNode != null)
    {
        if (_xmlNode as XmlElement != null)
            xpath.Push(_xmlNode.Name);

        _xmlNode = _xmlNode.ParentNode as XmlElement;
    }
    return string.Join("/", xpath.ToArray());
}

作者会将string修改为XMLNode,现在可以链接到好文件。

foreach (string path in file.Paths)
{
    XmlNode node = doc.SelectSingleNode(path);
    if (node.Attributes["type"].Value == "system" && node.Attributes["type"].Value != null)
    {
        node.Attributes["type"].Value = "session";
    }
}
//push to DB

XmlList类需要更改才能使用List而不是XmlNodeList