如何从头开始创建/编写一个简单的 XML 解析器?
Posted
技术标签:
【中文标题】如何从头开始创建/编写一个简单的 XML 解析器?【英文标题】:How to create/write a simple XML parser from scratch? 【发布时间】:2011-09-08 13:06:03 【问题描述】:除了代码示例,我想知道英文中简化的基本步骤是什么。
一个好的解析器是如何设计的?我知道正则表达式不应该在解析器中使用,但是正则表达式在解析 XML 中的作用有多大?
推荐使用什么数据结构?我应该使用链表来存储和检索节点、属性和值吗?
我想学习如何创建一个 XML 解析器,以便我可以用 D 编程语言编写一个。
【问题讨论】:
事件驱动解析器或文档构建解析器? 不幸的是,谷歌搜索“文档构建解析器”只会导致这个问题。如果您创建一个答案,也许您可以解决事件驱动解析器和文档构建解析器之间的区别。 我会注意到没有像“简单 XML”这样的语言。如果您打算解析 XML,那么您的解析器应该解析所有 XML,而不仅仅是其中的一部分。原因很简单:今天您可能只需要“简单”的 XML,但明天,您的代码可能会被要求解析“真正的 XML”。 @JohnSaunders 我认为他的意思是一个简单的解析器,而不是简单的 XML。 【参考方案1】:解析器和节点列表之间是有区别的。解析器是接收一堆纯文本 XML 并尝试确定其中存在哪些节点的部分。然后是保存节点的内部结构。在该结构之上的层中,您可以找到 DOM,即文档对象模型。这是构成 XML 文档的嵌套节点结构。解析器只需要知道通用 DOM 接口即可创建节点。
我不会为此使用正则表达式作为解析器。我认为最好的办法就是逐个字符地遍历字符串并检查你得到的是否与你应该得到的匹配。
但是为什么不使用任何现有的 XML 解析器呢?编码数据有很多可能性。很多例外。而且,如果您的解析器不能全部管理它们,那么 XML 解析器的称号就几乎不值得了。
【讨论】:
【参考方案2】:解析器必须满足您输入语言的需求。在您的情况下,简单的 XML。关于 XML,首先要了解的是它是上下文无关的并且绝对没有歧义,所有内容都包裹在两个标记之间,这就是 XML 出名的原因:它易于解析。最后,XML 总是简单地用树形结构表示。如前所述,您可以简单地解析您的 XML 并同时执行代码,或者解析 XML,生成树,然后根据该树执行代码。
D 提供了一种非常有趣的方式来非常轻松地编写 XML 解析器,例如:
doc.onStartTag["pointlight"] = (ElementParser xml)
debug writefln("Parsing pointlight element");
auto l = new DistantLight(to!int(xml.tag.attr["x"]),
to!int(xml.tag.attr["y"]),
to!int(xml.tag.attr["z"]),
to!ubyte(xml.tag.attr["red"]),
to!ubyte(xml.tag.attr["green"]),
to!ubyte(xml.tag.attr["blue"]));
lights ~= l;
xml.parse();
;
【讨论】:
我从未听说过一种名为“简单 XML”的语言。你能提供一个链接吗?是国际标准吗? 简单的 XML 是指对于基于事件的解析器,用户需要向其传递一些函数(startNode(name,attrs)
、endNode(name)
和 someText(txt)
可能通过接口)并在传递文件时在需要时调用它们
解析器将有一个 while 循环,该循环将在读取到 <
和直到 >
之间交替,并正确转换为参数类型
void parse(EventParser p, File file)
string str;
while((str = file.readln('<')).length !=0)
//not using a rewritable buffer to take advantage of slicing
//but it's a quick conversion to a implementation with a rewritable buffer though
if(str.length>1)p.someText(str.chomp('<'));
str = file.readln('>');
str = str.chomp('>');
//split str in name and attrs
auto parts = str.split();
string name = parts[0];
string[string] attrs;
foreach(attribute;parts[1..$])
auto splitAtrr = attribute.split("=");
attrs[splitAtrr[0]] = splitAtrr[1];
if(str[0] == '/')p.endNode(name);
else
p.startNode(name,attrs);
if(str[str.length-1]=='/')p.endNode(name);//self closing tag
您可以在基于事件的解析器之上构建 DOM 解析器,每个节点所需的基本功能是 getChildren 和 getParent getName 和 getAttributes(构建时使用 setter ;))
具有上述方法的 dom 解析器的对象:
class DOMEventParser : EventParser
DOMNode current = new RootNode();
overrides void startNode(string name,string[string] attrs)
DOMNode tmp = new ElementNode(current,name,attrs);
current.appendChild(tmp);
current = tmp;
overrides void endNode(string name)
asser(name == current.name);
current = current.parent;
overrides void someText(string txt)
current.appendChild(new TextNode(txt));
当解析结束时,rootnode 将拥有 DOM 树的根
注意:我没有放任何验证码以确保xml的正确性
编辑:属性的解析有一个错误,而不是在空格上拆分,正则表达式更好
【讨论】:
【参考方案4】:如果您不知道如何编写解析器,那么您需要做一些阅读。掌握任何有关编译器编写的书(许多最好的书都是 30 或 40 年前写的,例如 Aho 和 Ullmann),并学习有关词法分析和语法分析的章节。 XML 本质上没有什么不同,只是词汇和语法阶段不像在某些语言中那样清楚地相互隔离。
提醒一句,如果您想编写一个完全符合标准的 XML 解析器,那么您将花费 90% 的精力在规范的晦涩角落处理边缘情况,处理诸如大多数 XML 用户所使用的参数实体之类的事情甚至不知道。
【讨论】:
很好奇,什么数据结构最适合这项任务?我的直觉说是一棵通用树,不知道 OP 是否也想从头开始构建它,他/她可能会参与一个漫长的项目。【参考方案5】:由于 D 与 Java 密切相关,可能会生成一个带有 ANTLR 的 XML 解析器(因为很可能已经有用于 ANTLR 的 XML EBNF 语法,然后您可以使用这些),然后转换生成的 Java 解析器代码为 D,可以选择吗?至少这会给你一个起点,然后你可以努力尝试专门为 D 优化代码......
至少 ANTLR 并不像许多人想象的那么难。在对它一无所知后,我通过观看this great set of screencasts on ANTLR 的 3-4 开始。
顺便说一句,我发现 ANTLRWorks 可以轻松使用(与截屏视频中使用的 Eclipse 插件相反......但截屏视频内容仍然适用)。
只是我的 0.02c。
【讨论】:
【参考方案6】:文档中的第一个元素应该是序言。这说明了 xml 版本、编码、文件是否是独立的,也许还有其他一些东西。序言以<?
开头。
序言之后是带有元数据的标签。特殊标签,如 cmets、doctypes 和元素定义应以 <!
开头。处理指令以<?
开头。这里可以有嵌套标签,因为<!DOCTYPE
标签可以在 dtd 样式的 xml 文档中包含 <!ELEMENT
和 <!ATTLIST
标签——请参阅 Wikipedia 以获得完整的示例。
应该只有一个***元素。它是唯一一个前面没有<!
或<?
的。***元素之后可能有更多元数据标签;先处理这些。
对于显式解析:首先识别标签——它们都以<
开头——然后确定它是什么类型的标签以及它的闭包是什么样的。 <!--
是一个注释标签,除了它的结尾之外,不能有--
。 <?
以 ?>
结尾。 <!
以 >
结尾。重复一遍:<!DOCTYPE
可以在其关闭之前嵌套标签,并且可能还有其他我不知道的嵌套标签。
一旦你找到一个标签,你就会想找到它的结束标签。先检查标签是否自动关闭;否则,找到它的闭包。
对于数据结构:我建议使用树结构,其中每个元素都是一个节点,每个节点都有一个索引/映射的子元素列表。
显然,一个完整的解析器需要更多的研究;我希望这足以让您入门。
【讨论】:
以上是关于如何从头开始创建/编写一个简单的 XML 解析器?的主要内容,如果未能解决你的问题,请参考以下文章