【问题标题】:How to change the value of an attribute in a new XML document?如何更改新 XML 文档中属性的值?
【发布时间】:2015-03-22 23:31:40
【问题描述】:

我有一个数据库,其中有大量等待更新的 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 = 会话 & y = 系统

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

x = x -> x

x = y -> x

y = x -> x

y = y -> y

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

【问题讨论】:

  • 这在我不更新XML 时有效。我认为问题来自SelecNodes
  • 你能发一个 XML 的例子吗?
  • 您修改 XmlNode 属性的代码在理论上是正确的。在进行修改后,您可以通过Debug.WriteLine(doc.DocumentElement.OuterXml) 在调试器中看到这一点。因此,要么您的 XPath 查询不起作用,要么 XML 不是您所想的那样,或者您的“推送到数据库”不起作用。如果您向我们展示 XML,我们可能会提供更多帮助。

标签: c# .net xml xmldocument


【解决方案1】:

问题是您正在搜索属性名为type 且值为session 的节点——如果当前 值为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);
        }
    }

然后这样称呼它:

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

Update2 如果您不想一次将整个旧的XmlDocument 放在内存中(尽管我不明白为什么不这样做),您可以使用@987654331 构建一个元素查找表@ 属性,按路径索引,稍后使用:

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

(我忘记了 Zip 不在 .Net 3.5 中。)

【讨论】:

  • 会有一个更新,每XML会擦除一次,并设置一个具有相同节点但不同属性值的新文件,因此这两个操作不会在同一个文件上。 1:我得到XML上的节点路径2:擦除所有XML,3:使用节点路径设置值
  • @AnthonyLiard - 好的,我想我明白了。你能举出新旧 XML 的例子吗? 1-1中的元素是否按顺序对应,还是必须按名称对应。
  • 它们一般会按顺序对应,但有时不会,所以必须按名称对应。
  • child1.zip 是什么意思?
  • 您的解决方案有效,但不适用于我的情况,因为我的文件太大了。但是你真的很有用,你应该得到接受的答案,尤其是当你有这个stackoverflow.com/help/badges/226/unsung-hero?userid=3744182
【解决方案2】:

问题中代码的问题是XmlNodeList:它创建了一个链接到XML 文件的对象列表。因此,当文件被擦除时,XmlNodeList 毫无意义。因此,将 string 列表的 XmlNode 列表更改为这些 XPath 将起作用。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-29
    • 1970-01-01
    • 2014-03-21
    • 2010-09-26
    • 1970-01-01
    • 2014-03-23
    相关资源
    最近更新 更多