65.9K
CodeProject 正在变化。 阅读更多。
Home

从头开始编写一个简单的 XML 解析器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (9投票s)

2011年4月1日

CPOL

3分钟阅读

viewsIcon

45480

downloadIcon

487

出于性能考虑,我决定自己编写一个 XML 解析器。

引言

这是 .NET 中提供的一部分 XML 类的实现。 它可能很有用,因为它比 .NET 类运行得更快,但它的功能较少并且有一些错误。 如果您确切知道需要从文档中获取哪些数据,那么这是一种快速提取数据的方法。 如果您由于某种原因无法访问这些 .NET 库,例如您正在使用 silverlight 或嵌入式设备,库有限,XBox,windows phone 7 等等,它也很有用。

如果您从未使用过 .NET 类 XmlDocumentXPathNavigator,您需要了解 select 语法。 如果您知道,请随意跳过此部分。 如果没有,请先阅读此页面

本质上,Select 只是让您浏览 XML 节点的树。 但是,XML 中也有属性,例如 ex。

<shape><triangle sid="1/></shape> . 

sid 是一个属性,要选择它,路径将是

例子 1)

Select("shape/triangle") 
Evaluate( "@sid" )

返回 1

例子 2) (无属性)

<car><wheel>spinning</wheel></car> :
Select = "car"
evaluate "wheel" // no @ needed

返回 "spinning"

或者最快的方法,但没有范围保护

只需简单地评估("@sid"),根本不进行选择,就会搜索第一个出现的 @sid,而 MoveNext() 会执行您所想的操作。 此示例与 .NET 不同,并且明显地大幅提高性能,但只有在您了解文档时才能执行此操作。

范围

当我说范围检测时,我实际上是指将评估限制在节点范围内。.NET 类具有保护,并且在范围错误的情况下将返回 String.Empty。 此类禁用它,因此您可以优化搜索,而无需指定任何范围。 实际上,它并没有真正禁用它,解析算法中固有地存在一个关键差异。 此算法使用简单的 indexof() 进行搜索。 它不会编译和缓存任何内容,除了两个整数的开始和结束索引。 这种方法几乎不占用内存,并且在任何情况下都更快,但在范围模糊的情况下,它可能会返回错误的数据。 因此,您需要知道范围在您的选择中是唯一的。

用法

从文档中加载一些值类似于 .NET

public static void GetSingleValueFXmlDoc()
{
    string xml = "<car value=\"1\"><wheel sid=\"1\" bolts=\"4\">
	ddd</wheel><wheel sid=\"2\" bolts=\"4\">dddd</wheel></car>";

    DateTime dt = DateTime.Now;

    FXMLDocument doc = new FXMLDocument();
    doc.LoadXml(xml);
    doc.Select( "car/wheel" );
    string sid = doc.GetValueString("@sid"); // or doc.Evaluate(
    double df = doc.GetValueDouble("@bolts");
    
    string diff = DateTime.Now.Subtract(dt).ToString();
    Console.WriteLine("single values FXML Time\t\t={0}", diff);
} 

它基本上与 .NET 相同,但没有导航器。 在这里,我们加载文档,以与 xpath 导航器相同的语法选择路径,并调用 get value。 还有一个 evaluate 方法,但 getvalue 只是用一个简单的包装器返回您想要的类型。

读取所选节点中的所有属性

for (int i = 0; i < count; ++i)
{
    FXMLDocument doc = new FXMLDocument();
    doc.LoadXml(xml);
    doc.Select("car/wheel");

    foreach (FXMLDocument x in doc)
    {    
        string sid = x.GetValueString("@sid");
        double df = x.GetValueDouble("@bolts");
    }
}

工作原理

   for (int i = 0; i < nodes.Length; i++)
225      {
226          string nodeStart = String.Empty;
227          string nodeEnd = String.Empty;
228  
229          if ((nodes.Length > i + 1) && nodes[i + 1].IndexOf("@") == 0)
230          {
231              nodeStart = string.Format("{0}=\"", nodes[i + 1].Replace("@", ""));
232              nodeEnd = "\"";
233              isAttribute = true;
234          }
235          else if ((nodes.Length == 1) && nodes[i].IndexOf("@") == 0)
236          {
237              nodeStart = string.Format("{0}=\"", nodes[i].Replace("@", ""));
238              nodeEnd = "\"";
239              isAttribute = true;
240          }
241          else
242          {
243              // not last node
244              nodeStart = string.Format("{0}{1}", "<", nodes[i]);
245              nodeEnd = string.Format("{0}{1}{2}", "</", nodes[i], ">");
246              isAttribute = false;
247          }
248  
249          GetBetweenExcludeTokens(xmlDocument, nodeStart, nodeEnd, ref begin, out end);
250  
251          if (isAttribute)
252          {
253              return; //xmlbuffer;
254          }
255          int indexNextNode = (xmlDocument.IndexOf(">", begin)) + 1; // +1 to include >
256          int indexofEquals = xmlDocument.IndexOf("=", begin);
257          if (indexofEquals != -1 && (indexofEquals < indexNextNode))
258          {
259              if (begin < indexNextNode)
260              {
261                  begin += indexNextNode - begin;
262              }
263              else
264              {
265                  begin += begin - indexNextNode;
266              }
267          }
268          else
269          {
270              if (xmlDocument[begin] == ('>'))
271              {
272                  ++begin;
273              }
274          }
275      }
276  }

这是整个过程的主循环。 基本上,它只是遍历 select 语句的所有节点,并获取它们之间的文本,就是这样。 当然,对属性有特殊的处理,这真的使原本优雅的系统变成了一场真正的噩梦。 我敢打赌,在微软公司,当一些聪明人想要属性的那天,发生了一些争论。 无论如何,GetBetweenExcludeTokens 正在完成整个操作的核心部分,其他一切都只是语法支持。

关注点

这仅比 .NET 类快大约 30%,对于大多数人来说,这是不值得的,因为它并不完全兼容。 但是,在示例 GetSingleValueFXmlDoc 中,有一个加速的秘诀。 因为我们知道文档仅包含 wheels->bolts(bolts 是唯一的),为什么还要费心选择 car/wheel,有了这个,你可以只调用 doc.Evaluate( @bolts ) 而不选择任何东西,这将比 .NET 类提高至少 100% 的性能。 这实际上是它最初的意图,但是,随着我的进行,我遇到了很多陷阱,当您打开 ValidateSyntax 时,这会稍微减少它们。

我的第一个版本只是一个 static 方法,可能更好,我希望我保存了它,但是你不能在一个 static 实现中很好地 MoveNext(虽然不必 new 它很好)。 您可以很容易地将 getSubNodes 修改为 static

© . All rights reserved.