大型XML文件,XmlDocument不可行但需要能够搜索

时间:2009-02-17 16:02:27

标签: c# xml mobile compact-framework xmltextreader

我正在努力使用一个合理的逻辑循环来从XML文件中剥离出太大的节点,以免与支持.NET类的XPath一起使用。

我正在尝试使用代码执行相同但使用XmTextReader替换我拥有的单行代码(使用XPath查询字符串调用SelectNodes)。

我必须按照先前使用的XPath查询(仅供参考)的图示使用几个级别:

ConfigurationRelease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Settings/Setting[Name='MySettingName']

我觉得这很烦人但很简单。但是,我似乎无法使循环正确。

我需要获取一个节点,检查其下的节点以查看该值是否与目标字符串匹配,然后再向下走,如果不存在则跳过该分支。

事实上,我认为我的问题是,如果我没有把它分开,我不知道怎么忽略一个分支。我不能允许它走不相关的分支,因为元素名称不是唯一的(如XPath查询所示)。

我认为我可以保留一些布尔,例如当我点击Profile节点时,bool expectingProfileName被设置为true。但是,如果它不是我想要的特定配置文件节点,我就无法离开那个分支。

所以...希望这对某人有意义......我一直盯着这个问题几个小时,可能只是遗漏了一些明显的东西......

我想发布一部分文件,但无法弄清楚结构的粗略程度:

ConfigRelease > Profiles > Profile > Name > Screens > Screen > Settings > Setting > Name

我将知道ProfileName,ScreenName和SettingName,我需要设置节点。

我试图避免在一次点击中读取整个文件,例如在应用程序启动时,因为其中一半的东西将永远不会被使用。我也无法控制生成xml文件的内容,因此无法将其更改为生成多个较小的文件。

任何提示将不胜感激。

更新

我重新打开了这个。一张海报建议XPathDocument应该是完美的。 Unfortunatley,我没有提到这是一个移动应用程序,并且不支持XPathDocument。

大多数标准文件都不大,这就是系统最初被编码为使用XmlDocument的原因。它目前是4MB,显然大到足以在移动应用程序加载到XmlDocument时崩溃。它可能就像它现在出现的那样,因为文件会变得更大。无论如何,我现在正在尝试DataSet建议,但仍然对其他想法持开放态度。

更新2

我怀疑是因为有不少人说他们不会指望这么大的文件会让系统崩溃。进一步的实验表明,这是间歇性的崩溃。昨天它每次都崩溃了,但今天早上我重置设备后,我无法重现它。我现在正试图找出一套可靠的生殖步骤。并且还决定了解决问题的最佳方法,我相信它仍然存在。我不能放弃它,因为如果应用程序无法访问此文件,它是无用的,我不认为可以告诉我的用户,当我的应用程序运行时,他们无法在他们的设备上运行任何其他内容... ....

6 个答案:

答案 0 :(得分:9)

查看XPathDocument

XPathDocument比XmlDocument更轻量级,并且针对只读XPath查询进行了优化。

答案 1 :(得分:3)

好的,我被这个娱乐了,所以我一起破解了一些代码。它并不漂亮,只是真正支持这一个用例,但我认为它可以完成您正在寻找的工作,并且可以作为一个开端的平台。我还没有彻底测试过它。 最后,您需要修改代码以使其返回内容(请参阅名为Output()的方法)。

以下是代码:

using System;

using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace XPathInCE
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                if (args.Length != 2)
                {
                    ShowUsage();
                }
                else
                {
                    Extract(args[0], args[1]);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0} was thrown", ex.GetType());
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }

            Console.WriteLine("Press ENTER to exit");
            Console.ReadLine();
        }

        private static void Extract(string filePath, string queryString)
        {
            if (!File.Exists(filePath))
            {
                Console.WriteLine("File not found! Path: {0}", filePath);
                return;
            }

            XmlReaderSettings settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true };
            using (XmlReader reader = XmlReader.Create(filePath, settings))
            {
                XPathQuery query = new XPathQuery(queryString);
                query.Find(reader);
            }
        }

        static void ShowUsage()
        {
            Console.WriteLine("No file specified or incorrect number of parameters");
            Console.WriteLine("Args must be: Filename XPath");
            Console.WriteLine();
            Console.WriteLine("Sample usage:");
            Console.WriteLine("XPathInCE someXmlFile.xml ConfigurationRelease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Settings/Setting[Name='MySettingName']");
        }

        class XPathQuery
        {
            private readonly LinkedList<ElementOfInterest> list = new LinkedList<ElementOfInterest>();
            private LinkedListNode<ElementOfInterest> currentNode;

            internal XPathQuery(string query)
            {
                Parse(query);
                currentNode = list.First;
            }

            internal void Find(XmlReader reader)
            {
                bool skip = false;
                while (true)
                {
                    if (skip)
                    {
                        reader.Skip();
                        skip = false;
                    }
                    else
                    {
                        if (!reader.Read())
                        {
                            break;
                        }
                    }
                    if (reader.NodeType == XmlNodeType.EndElement
                            && String.Compare(reader.Name, currentNode.Previous.Value.ElementName, StringComparison.CurrentCultureIgnoreCase) == 0)
                    {
                        currentNode = currentNode.Previous ?? currentNode;
                        continue;
                    }
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        string currentElementName = reader.Name;
                        Console.WriteLine("Considering element: {0}", reader.Name);

                        if (String.Compare(reader.Name, currentNode.Value.ElementName, StringComparison.CurrentCultureIgnoreCase) != 0)
                        {
                            // don't want
                            Console.WriteLine("Skipping");
                            skip = true;
                            continue;
                        }
                        if (!FindAttributes(reader))
                        {
                            // don't want
                            Console.WriteLine("Skipping");
                            skip = true;
                            continue;
                        }

                        // is there more?
                        if (currentNode.Next != null)
                        {
                            currentNode = currentNode.Next;
                            continue;
                        }

                        // we're at the end, this is a match! :D
                        Console.WriteLine("XPath match found!");
                        Output(reader, currentElementName);
                    }
                }
            }

            private bool FindAttributes(XmlReader reader)
            {
                foreach (AttributeOfInterest attributeOfInterest in currentNode.Value.Attributes)
                {
                    if (String.Compare(reader.GetAttribute(attributeOfInterest.Name), attributeOfInterest.Value,
                                       StringComparison.CurrentCultureIgnoreCase) != 0)
                    {
                        return false;
                    }
                }
                return true;
            }

            private static void Output(XmlReader reader, string name)
            {
                while (reader.Read())
                {
                    // break condition
                    if (reader.NodeType == XmlNodeType.EndElement
                        && String.Compare(reader.Name, name, StringComparison.CurrentCultureIgnoreCase) == 0)
                    {
                        return;
                    }

                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        Console.WriteLine("Element {0}", reader.Name);
                        Console.WriteLine("Attributes");
                        for (int i = 0; i < reader.AttributeCount; i++)
                        {
                            reader.MoveToAttribute(i);
                            Console.WriteLine("Attribute: {0} Value: {1}", reader.Name, reader.Value);
                        }
                    }

                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        Console.WriteLine("Element value: {0}", reader.Value);
                    }
                }
            }

            private void Parse(string query)
            {
                IList<string> elements = query.Split('/');
                foreach (string element in elements)
                {
                    ElementOfInterest interestingElement = null;
                    string elementName = element;
                    int attributeQueryStartIndex = element.IndexOf('[');
                    if (attributeQueryStartIndex != -1)
                    {
                        int attributeQueryEndIndex = element.IndexOf(']');
                        if (attributeQueryEndIndex == -1)
                        {
                            throw new ArgumentException(String.Format("Argument: {0} has a [ without a corresponding ]", query));
                        }
                        elementName = elementName.Substring(0, attributeQueryStartIndex);
                        string attributeQuery = element.Substring(attributeQueryStartIndex + 1,
                                    (attributeQueryEndIndex - attributeQueryStartIndex) - 2);
                        string[] keyValPair = attributeQuery.Split('=');
                        if (keyValPair.Length != 2)
                        {
                            throw new ArgumentException(String.Format("Argument: {0} has an attribute query that either has too many or insufficient = marks. We currently only support one", query));
                        }
                        interestingElement = new ElementOfInterest(elementName);
                        interestingElement.Add(new AttributeOfInterest(keyValPair[0].Trim().Replace("'", ""),
                            keyValPair[1].Trim().Replace("'", "")));
                    }
                    else
                    {
                        interestingElement = new ElementOfInterest(elementName);
                    }

                    list.AddLast(interestingElement);
                }
            }

            class ElementOfInterest
            {
                private readonly string elementName;
                private readonly List<AttributeOfInterest> attributes = new List<AttributeOfInterest>();

                public ElementOfInterest(string elementName)
                {
                    this.elementName = elementName;
                }

                public string ElementName
                {
                    get { return elementName; }
                }

                public List<AttributeOfInterest> Attributes
                {
                    get { return attributes; }
                }

                public void Add(AttributeOfInterest attribute)
                {
                    Attributes.Add(attribute);
                }
            }

            class AttributeOfInterest
            {
                private readonly string name;
                private readonly string value;

                public AttributeOfInterest(string name, string value)
                {
                    this.name = name;
                    this.value = value;
                }

                public string Value
                {
                    get { return value; }
                }

                public string Name
                {
                    get { return name; }
                }
            }
        }
    }
}

这是我正在使用的测试输入:

<?xml version="1.0" encoding="utf-8" ?>
<ConfigurationRelease>
  <Profiles>
    <Profile Name ="MyProfileName">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>Good stuff</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="SomeProfile">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="Boring">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="SomeProfile">
      <Screens>
        <Screen Id="Boring">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="Boring">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
  </Profiles>
</ConfigurationRelease>

这是我得到的输出。

C:\Sandbox\XPathInCE\XPathInCE\bin\Debug>XPathInCE MyXmlFile.xml ConfigurationRe
lease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Set
tings/Setting[Name='MySettingName']
Considering element: ConfigurationRelease
Considering element: Profiles
Considering element: Profile
Considering element: Screens
Considering element: Screen
Considering element: Settings
Considering element: Setting
XPath match found!
Element Paydirt
Attributes
Element value: Good stuff
Considering element: Profile
Skipping
Considering element: Profile
Skipping
Considering element: Profile
Skipping
Press ENTER to exit

我在桌面上运行它,但它是我生成的CF 2.00 .exe所以它应该在CE上正常工作。 正如你所看到的那样,当它不匹配时会跳过它,因此它不会遍历整个文件。

非常感谢任何人的反馈,特别是如果人们有指示使代码更简洁。

答案 2 :(得分:2)

尝试将文件加载到数据集中:

DataSet ds = new Dataset();
ds.ReadXml("C:\MyXmlFile.xml")

然后你可以使用linq来搜索它。

答案 3 :(得分:2)

我正在添加此问题,因为问题已经解决,但所选解决方案与目前列出的任何内容都不匹配。

我们的技术架构师已经解决了这个问题,并决定我们永远不应该首先实现Xml。这一决定部分是由于这个问题,也是由于对数据传输费用水平的一些抱怨。

他的判决是我们应该实现一种针对查询的大小和速度进行了优化的自定义文件格式(带索引)。

因此,问题暂时搁置,直到该工作得到批准并正确推出。

现在结束。

答案 4 :(得分:0)

将其加载到数据集中是行不通的 - 这将占用更多内存。

当遇到类似问题时,我使用了XmlReader并在加载时构建了内存索引。我展示了索引,然后当用户点击链接或激活搜索时,我再次使用XmlReader重新读取XML文档,并加载相应的子集。

这听起来很费劲,我想在某些方面也是如此。它将CPU周期换成内存。但它的工作原理,应用程序足够响应。数据大小只有2mb,不是那么大。但是我用数据集获得了OOM。然后我去了XmlSerializer并且工作了一段时间,但我再次击中了一个OOM。所以我终于回到了这个自定义索引的东西。

答案 5 :(得分:0)

您可以实现基于sax的解析器 - 这样您在解析XML时只会获取您感兴趣的分支。这将是最好的方法,因为它不会将整个xml作为文档加载。

最理想的是,您可以根据需要设计自定义解析器,并在一次传递中对所有内容进行所有解析 - 例如,如果您可能对特定节点感兴趣,请稍后保存对它们的引用,以便您可以从那里开始,而不是再次重新训练或遍历。

这里的缺点是它有点自定义编程。

好处是,您只会阅读您感兴趣的内容并根据您的要求处理xml文档。您还可以在完成文档传递之前开始处理结果。这非常适合根据文档内容启动工作线程。示例:您可以将元素的全部内容作为另一个XML文档的根,然后单独加载它(使用xpath等)。您可以将内容复制到缓冲区中,然后将其交给工作人员进行处理等。

我很久以前使用过libxml2 for C,但也有C#绑定(以及许多其他语言)。