从头开始编写一个简单的 XML 解析器
出于性能考虑,我决定自己编写一个 XML 解析器。
引言
这是 .NET 中提供的一部分 XML 类的实现。 它可能很有用,因为它比 .NET 类运行得更快,但它的功能较少并且有一些错误。 如果您确切知道需要从文档中获取哪些数据,那么这是一种快速提取数据的方法。 如果您由于某种原因无法访问这些 .NET 库,例如您正在使用 silverlight 或嵌入式设备,库有限,XBox,windows phone 7 等等,它也很有用。
如果您从未使用过 .NET 类 XmlDocument
和 XPathNavigator
,您需要了解 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
。