【问题标题】:What is the best way to store and play back XML nodes?存储和回放 XML 节点的最佳方式是什么?
【发布时间】:2013-02-14 18:22:54
【问题描述】:

我有一些看起来像这样的 XML(高度简化):

<?xml version="1.0"?>
<example>
    <shortcuts>
        <shortcut name="shortcut1">
            <property name="name1" value="value1" />
            <property name="name2" value="value2" />
        </shortcut>
    </shortcuts>
    <data>
        <datum name="datum1">
            <property name="name1" value="value1" />
            <property name="name2" value="value2" />    
        </datum>
        <datum name="datum2">
            <shortcutRef name="shortcut1" />
        </datum>
        <datum name="datum3">
            <shortcutRef name="shortcut1" />
            <property name="name3" value="value3" />    
        </datum>
    </data>
</example>

如您所见,它的结构使得可以定义由一个或多个属性组成的“快捷方式”。然后可以使用属性或一个或多个快捷方式或两者的组合来明确描述数据(并且没有特定的顺序)。

我想用 XmlReader 解析它(XmlDocument 会更容易,但在这里不起作用,因为 XML 文件太大)。我认为这样做的一个好方法是将每个快捷方式的 XML 子树存储在由唯一的快捷方式名称键入的字典中。然后,当它们被引用时,我可以通读该子树 XmlReader 而不是主树。但是,子树 XmlReader 仍必须链接到主 XmlReader,因为输出的 XML 不是我所期望的。这是我的代码:

using(XmlReader xml = XmlReader.Create("example.xml"))
{
    Dictionary<string, XmlReader> shortcuts = new Dictionary<string, XmlReader>();
    xml.ReadToDescendant("shortcuts");
    xml.ReadToDescendant("shortcut");
    do
    {
        shortcuts.Add(xml.GetAttribute("name"), xml.ReadSubtree());
    } while(xml.ReadToNextSibling("shortcut"));

    xml.ReadToFollowing("data");
    while(xml.ReadToFollowing("datum"))
    {
        Console.WriteLine(xml.GetAttribute("name"));

        XmlReader datum = xml.ReadSubtree();
        while(datum.Read())
        {
            if(datum.Name == "property")
            {
                Console.WriteLine(datum.GetAttribute("name") + ':' + datum.GetAttribute("value"));
            }
            else if(datum.Name == "shortcutRef")
            {
                XmlReader shortcut_ref = shortcuts[datum.GetAttribute("name")];
                while(shortcut_ref.ReadToFollowing("property"))
                {
                    Console.WriteLine(shortcut_ref.GetAttribute("name") + ':' + shortcut_ref.GetAttribute("value"));
                }
            }
        }
    }
}

解析以这种方式构造的 XML 的最佳方法是什么?

【问题讨论】:

  • 首选的输出数据结构是什么(即解析的结果)?
  • 此时我只想能够解析与每个数据关联的每个属性的名称和值。

标签: c# .net xml memory-management xmlreader


【解决方案1】:

您可以按照 Mathieson 的建议使用 LinqToXml。这是使用查找的示例。

XElement root = XElement.Load(file); // or .Parse(string)
var shortcuts = root.Descendants("shortcut").SelectMany(s =>
    s.Elements("property").ToLookup(
        k => k.Parent.Attribute("name").Value,
        v => v.Select(p => new
        {
            Name = p.Attribute("name").Value,
            Value = p.Attribute("value").Value
        })));

这将产生一个类似于字典的结构,但查找具有多个键值。因此,您将通过快捷方式名称查找所有属性。

【讨论】:

  • 不会和 XmlDocument 有同样的内存问题吗?
  • 什么样的内存问题?我向你保证它不会泄露内存
  • 另外,如果您有疑虑,您应该单独询问有关内存和各种 xml 阅读器的问题。
  • XML 文件非常庞大——目前最多可以使用,未来可能会变得更大。将所有内容加载到内存中可能会令人望而却步。我认为必须有一种方法可以使用 XmlReader 来做到这一点。
  • 我使用与您在我的 XML 文件中查找数据相同的技术。虽然大小只有 1MB 左右,但速度快如闪电。您可以通过将选择列表变量作为 XElement 参数传递来创建返回查找数据的函数。如果您需要更多答案,请告诉我。
【解决方案2】:

你想要做什么并不完全清楚——但既然你使用“回放”这个词,那么我猜你不需要将来自 XML 节点(数据/数据)的所有值存储在内存中(你可以在使用后丢弃它们),但是您需要缓存快捷方式属性,以便在引用它们时可以重新迭代它们……您刚刚拥有它,但不是存储 XML 节点,而是将对象存储在字典。

public class Property
{
    public string Name { get; set; }
    public string Value { get; set; }
}

public class Shortcut
{
    public List<Property> Properties = new List<Property>();
}

class Program
{
    static void Main(string[] args)
    {
        FileStream fs = new FileStream(@"c:\temp\example.xml", FileMode.Open, FileAccess.Read);
        XmlTextReader reader = new XmlTextReader(fs);

        Dictionary<string, Shortcut> ShortcutDictionary = new Dictionary<string, Shortcut>();

        while (reader.Read())
        {
            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "shortcuts")
            {
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "shortcut")
                    {
                        Shortcut shortcut = new Shortcut();
                        ShortcutDictionary.Add(reader.GetAttribute("name"), shortcut);
                        while (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "property")
                                shortcut.Properties.Add(new Property() { Name = reader.GetAttribute("name"), Value = reader.GetAttribute("value") });
                            else if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "shortcut")
                                break;
                        }
                    }
                    else if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "shortcuts")
                        break;
                }
            }

            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "data")
            {
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "datum")
                    {
                        while (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "property")
                            {
                                Console.WriteLine(reader.GetAttribute("name") + ':' + reader.GetAttribute("value"));
                            }
                            else if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "shortcutRef")
                            {
                                foreach (Property property in ShortcutDictionary[reader.GetAttribute("name")].Properties)
                                    Console.WriteLine(property.Name + ':' + property.Value);
                            }
                            else if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "datum")
                                break;
                        }
                    }
                    else if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "data")
                        break;
                }
            }
        }

        reader.Close();
        fs.Close();
    }
}

否则,如果不是这样,那么您正在尝试以随机访问方式访问串行数据。您最好的选择是将数据转换/保存到数据库中。像 SQLite 这样的东西可以做到。

【讨论】:

  • 我希望得到一个答案,它在从快捷方式和显式属性中迭代属性之间非常无缝,以最大限度地减少所需的代码量。但这确实符合我的所有要求,所以你得到了赏金。
【解决方案3】:

我会使用 xml 序列化。只需创建一个具有所需结构的 POCO 类(在这种情况下,是一个带有“数据”和“快捷方式”列表的“示例”类),注释一些成员,以便它们可以呈现为属性并调用 xml 序列化。看看这个:

如何创建类:http://www.codeproject.com/Articles/483055/XML-Serialization-and-Deserialization-Part-1

如何序列化和反序列化:http://www.codeproject.com/Articles/347758/XML-Serialization-and-Deserialization

【讨论】:

    【解决方案4】:

    通常可以通过两种主要的理智方式来解析 XML 文件:
    1. DOM(文档对象模型)
    2. SAX(XML 的简单 API)

    区别
    DOM 解析器根据输入文档在内存中创建树结构,然后等待来自客户端的请求。但是 SAX 解析器不会创建任何内部结构。相反,它将输入文档的组件的出现作为事件,并告诉客户端它在读取输入文档时读取的内容。
    DOM 解析器始终为客户端应用程序提供整个文档,无论客户端实际需要多少,但 SAX 解析器始终只为客户端应用程序提供任何给定时间的文档片段。
    使用 DOM 解析器,客户端应用程序中的方法调用必须是显式的并形成一种链。但是对于 SAX,某些特定方法(通常被 cient 覆盖)将在某些特定事件发生时以一种称为“回调”的方式自动(隐式)调用。
    这些方法不必由客户端显式调用,尽管我们可以显式调用它们。

    DOM
    1. 节点树
    2. 内存:占用更多内存,优先用于小型 XML 文档
    3. 运行时变慢
    4. 存储为对象
    5. 编程简单
    6. 易于导航
    SAX
    1. 事件顺序
    2. 不使用大型文档首选的任何内存
    3. 运行速度更快
    4. 对象被创建
    5. 需要编写创建对象的代码
    6. 向后导航是不可能的,因为它顺序处理文档

    实现.Net框架
    XmlReader(somehow)和XmlDocuments是基于DOM模型构建的, MSDN refrence.
    我还没有找到用于 SAX 的官方 .Net 框架 API,但您可以使用 Towards a Declarative SAX FrameworkSax for .Net

    【讨论】:

    • -1 没有回答问题,OP 知道 SAX 和 DOM 之间的区别,他只是想知道如何使用 SAX 完成这项任务,因为文件大小对于 DOM 来说太大了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-04
    • 1970-01-01
    • 1970-01-01
    • 2013-06-14
    • 2011-03-05
    • 2013-10-25
    • 2017-03-09
    相关资源
    最近更新 更多