3

我正在努力使用合理的逻辑循环来从 XML 文件中删除节点,该文件太大而无法与支持 .NET 类的 XPath 一起使用。

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

如先前使用的 XPath 查询所示(仅供参考),我必须往下走几个级别:

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

我认为这会很烦人但很简单。但是,我似乎无法正确循环。

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

事实上,我认为我的问题是,如果我对分支不感兴趣,我不知道如何忽略它。我不能让它走不相关的分支,因为元素名称不是唯一的(如 XPath 查询所示)。

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

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

我想发布文件的一部分,但无法弄清楚结构大致如何:

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

我会知道 ProfileName、ScreenName 和 SettingName,并且我需要 Setting 节点。

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

任何提示将不胜感激。

更新

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

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

更新 2

我很怀疑,因为很多人都说他们不会期望这么大的文件会导致系统崩溃。进一步的实验表明,这是一次间歇性崩溃。昨天它每次都崩溃,但今天早上我重置设备后,我无法重现它。我现在正试图找出一套可靠的繁殖步骤。并决定处理我确信仍然存在的问题的最佳方法。我不能就这样离开它,因为如果应用程序无法访问该文件,它就没用了,而且我认为无法告诉我的用户,当我的应用程序运行时,他们无法在他们的设备上运行其他任何东西...... ……

4

6 回答 6

10

看看XPathDocument

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

于 2009-02-17T16:12:39.603 回答
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 上正常工作。如您所见,当它不匹配时它会跳过,因此它不会遍历整个文件。

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

于 2009-02-23T22:44:27.083 回答
2

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

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

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

于 2009-02-17T16:09:12.043 回答
2

我添加了这个,因为问题现在已经死了,但所选的解决方案与迄今为止列出的任何内容都不匹配。

我们的技术架构师接管了这个问题,并决定我们一开始就不应该实现 Xml。这一决定部分是由于这个问题,但也由于一些对数据传输费用水平的抱怨。

他的结论是我们应该实现一种自定义文件格式(带有索引),优化了查询的大小和速度。

因此,在该工作获得批准并适当指定之前,该问题被搁置。

暂时结束。

于 2009-02-25T19:29:22.623 回答
0

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

当遇到类似情况时,我使用了 XmlReader 并在加载时建立了一个内存索引。我提供了索引,然后当用户单击链接或激活搜索时,我再次使用 XmlReader 重新读取 XML 文档,并加载适当的子集。

这听起来很费力,我想在某些方面确实如此。它用 CPU 周期换取内存。但它有效,并且该应用程序响应速度足够快。数据大小只有2mb,不算大。但我得到了带有数据集的OOM。然后我去了 XmlSerializer 并且工作了一段时间,但我再次遇到了 OOM。所以我终于回到了这个自定义索引的事情上。

于 2009-03-09T06:43:27.073 回答
0

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

理想情况下,您可以根据需要围绕您的需求设计自定义解析器 - 并一次完成所有内容的解析 - 例如,如果您可能对特定节点感兴趣,请稍后保存对它们的引用,以便稍后从那里开始而不是重新解析或遍历。

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

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

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

于 2009-03-09T07:35:41.550 回答