如何做DTD的校验
DTD对XML文档的结构定义主要体现在两个方面,对子节点类型的定义和对属性的定义。一个xml parser要实现DTD校验,必然要实现对这两种DTD定义的校验。先考虑DTD子节点类型校验的情况。
DTD声明对子节点机构的定义主要分为几种类型:
<!ELEMENT A ANY>
A节点下可以包含任意节点类型,最简单的情况。
<!ELEMENT A (#PCDATA)>
A节点只能包含文本信息。
<!ELEMENT A (B,C)>
A节点可以而且必须包含B和C节点,并且B节点必须位于C节点之前。
<!ELEMENT A (B*,C)>
A节点后面可以包含任意数目的B节点,然后紧跟着一个C节点。
<!ELEMENT A (B?,C+)*>
这种情况比较复杂,先忽略最外层的*,B?表示可以有一个且最多一个B节点,然后至少一个C节点。
然后综合考虑最外层的*,这种组合可以重复出现的次数为0,1,2,....试着枚举一下,可能的情况是:
BC,BCC,...,C,CC,CCC,...,BBC,BBCC,BBCCC...,BCBC,BCBCBC,...,...
如果要进行DTD子节点类型的校验,显然就是这种*,?,+带来的情况比较复杂。其实可以发现,这种声明方式本身就是一种正则表达式的表达方式。那么我们可否通过正则表达式的校验来实现DTD子节点的类型校验呢?答案是肯定的。
看一个例子,<!ELEMENT A (B?,C+)*>,对于这种情况,一般的XML PARSER生成的内存数据结构都是:
两个类型层次:
DTDElementDecl
DTDElementDeclNode
DTDElementDeclNode代表一个声明的子节点,而DTDELementDecl代表一个完整的节点声明。
那么对于上面的表达式,产生的结构是:
DTDElementDeclNode B;
DTDElementDeclNode C;
B.setName("B");
B.setCountType(enumOneOrZero);
C.setName("C");
c.SetCountType(enumOneOrMore);
DTDElementDeclNode D;
D.setCountType(enumZeroOrMore);
D.addChild(B);
D.addChild(C);
DTDElementDecl A;
A.addChild(D);
现在可以看到,DTDElementDecl下面有一个子节点,这个子节点可以是任意个。这个子节点包含自己的2类子节点,循环下去。
如果使用正则表达式的方式来做校验,首先需要把这种层次结构翻译成一个正则表达式,比如上面的结构可以表达为:(B?C+)*,非常简单,可以使用一个正则表达式分析引擎来分析其结构(比如boost regex)。
有了正则表达式以后,就可以对xml文档进行校验了,但是由于目前的正则表达式引擎都只能做字符串的匹配工作,所以还需要把xml文档中相应的节点的层次结构转换为一个相应的字符串,也举一个例子:
<A>
<B/>
<C/>
<C/>
<C/>
<B/>
<C/>
</A>
由以前的分析,可以看到这是一个符合<!ELEMENT A (B?,C+)*>的层次结构,转换为相应的正则表达式字符串,可以表示为:
BCCCBC.
最后的工作就是,用(B?C+)*来匹配BCCCBC,相信任意一个正则表达式引擎都会判定为合法匹配。
以上是本人一个xml parser中实现DTD元素结构校验的实现方式,至于属性的校验,会在后一篇blog中给出。
如果有兴趣看到相应的源代码,请给出mail地址。