如何从头创建/编写一个简单的XML解析器?

时间:2011-06-04 22:25:57

标签: xml d xml-parsing

如何从头开始创建/编写简单的XML解析器?

我想知道英语中简化的基本步骤,而不是代码示例。

如何设计好的解析器?我知道正则表达式不应该在解析器中使用,但正则表达式在解析XML中的作用是多少?

建议使用的数据结构是什么?我应该使用链表来存储和检索节点,属性和值吗?

我想学习如何创建XML解析器,以便用D编程语言编写一个。

6 个答案:

答案 0 :(得分:10)

如果您不知道如何编写解析器,那么您需要进行一些阅读。掌握任何关于编译器编写的书(很多最好的书是在30或40年前编写的,例如Aho和Ullmann)并研究词汇分析和语法分析的章节。 XML基本上没有什么不同,除了词法和语法阶段不像某些语言那样彼此明显隔离。

一句警告,如果你想编写一个完全一致的XML解析器,那么90%的努力将用于在规范的不明确的角落处理边缘案例,处理诸如大多数XML用户不是参数实体之类的事情。甚至不知道。

答案 1 :(得分:6)

解析器和节点列表之间存在差异。解析器是一个部分,它接受一堆纯文本XML并尝试确定那里有哪些节点。然后是一个保存节点的内部结构。在该结构的一个层中,您可以找到DOM,即文档对象模型。这是构成XML文档的嵌套节点的结构。解析器只需知道通用DOM接口即可创建节点。

我不会使用正则表达式作为解析器。我认为最好的事情就是通过char遍历字符串char并检查你得到的是否与你应该得到的匹配。

但为什么不使用任何现有的XML解析器呢?编码数据有很多种可能性。很多例外。如果您的解析器不管理它们,则几乎不值得使用XML解析器。

答案 2 :(得分:5)

for和基于事件的解析器,用户需要通过接口向其传递一些函数(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的正确性

编辑:解析属性中有一个错误,而不是在空格上拆分正则表达式更适合

答案 3 :(得分: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();
};

答案 4 :(得分:0)

由于D与Java密切相关,可能会生成一个带有ANTLR的XML解析器(因为很可能已经为ANTLR编写了XML EBNF语法,然后您可以使用这些语法),然后转换生成的Java解析器代码到D,可能是一个选项吗?至少那会给你一个起点,然后你可以努力尝试专门为D ...优化代码...

至少ANTLR并不像许多人想象的那么难。通过观看this great set of screencasts on ANTLR的3-4,我开始对它一无所知。

顺便说一下,我发现ANTLRWorks可以轻松使用(与截屏视频中使用的Eclipse插件相反......但截屏视频内容仍适用)。

只是我的0.02c。

答案 5 :(得分:0)

文档中的第一个元素应该是序言。这说明了xml版本,编码,文件是否是独立的,以及其他一些东西。 prolog以<?打开。

在prolog之后,有标签和元数据。注释,文档类型和元素定义等特殊标记应以<!开头。处理说明以<?开头。此处可以嵌套标记,因为<!DOCTYPE标记可以在dtd样式的xml文档中包含<!ELEMENT<!ATTLIST标记 - 有关详细示例,请参阅Wikipedia

应该只有一个顶级元素。这是唯一一个没有<!<?的人。顶级元素后面可能有更多的元数据标签;首先处理这些。

对于显式解析:首先识别标签 - 它们都以<开头 - 然后确定它是什么类型的标签以及它的闭包是什么样的。 <!--是一个评论标记,除了结尾之外无法将--放在任何地方。 <??>结尾。 <!>结尾。重复:<!DOCTYPE可以在关闭之前嵌套标签,并且可能还有其他我不知道的嵌套标签。

找到标签后,您会想要找到它的结束标签。检查标签是否先自动关闭;否则,找到它的关闭。

对于数据结构:我建议使用树结构,其中每个元素都是一个节点,每个节点都有一个索引/映射的子元素列表。

显然,完整的解析器需要更多的研究;我希望这足以让你开始。

相关问题