CMarkup定位解释
三年前,我写过一篇关于Cmarkup定位的解释,现在我用这篇文章代替那篇。
int nStart;
int nLength;
int nTagLengths;
int nFlags;
int iElemParent;
int iElemChild;
int iElemNext;
int iElemPrev;
前三个整数告诉我们了在文档中,元素的起始位置,它的长度以及开始和结束标签的长度,因为nStart是一个32bit的整数,所以文档 的最大尺寸可以有2G,并且同样,最大元素的长度是一样的。整数nTagLengths被分成两部分,22bit(4M)用于开始标签(开始标签可以包含属性),另外10bit(1K)用于结束标签。在下列元素中,开始标签的长度是14,结束标签是8.,整个元素的长度是29.
<topic id="5">triumph</topic>
整数nFlags的低16位实际存储的是元素的深度或层次,高16位是特殊标识。根元素是层,根元素的孩子是1层等等 ,特殊标识告诉我们元素是第一个兄弟或最后一个兄弟、或空元素、以及如果这个元素已经被删除了(这样的结构能够被恢复)
四个iElem整形变量链接到周围的相关元素,iElemParent指向父元素,iELemChild指向第一个子元素,iElemNext指向下一个元素,当这个元素是最后一个兄弟时,iElemNext是,当元素不是第一个兄弟元素时,iElemPrev指向其前一个兄弟元素,如果元素是第一个兄弟元素,iElemPrev指向最后一个兄弟元素。因此,顺着iElemChild的链接以及从那里沿着iElemPrev的链接,可以得到父元素的最后一个子元素。
如果你熟悉像这样用树结点链接在一起的工作方式,你将明白这个地图的效果。这个设计对于树层次的回来操作是有效的,再通过兄弟元素形成一个环,但是,对于随机访问第n个子元素将需要先循环这个元素之前的所有兄弟元素。一旦被建立起来,在文档中随意的导航将不再需要费时的分析。
当一个文档被分析时,就产生了这些信息,因此当文档被修改时,这些信息也修改。例如,增加一个属性,开始标签的长度改变了,元素的长度改变了,以及所有后面的和包含的元素都要被调整。如果删除一个元素,领衔值改变了,还有其前一个元素的iElemNext也被修改以绕过删除的元素等等。
深入Markup分析器
虽然Markup经常被叫做是“分析器”,但是分析保是Cmarkup功能中的一部分,另外Cmarkup还支持文档的导航、创建和修改,还有其它如文件I/O、字符集及64位编码转换等功能。然而分析器是Cmarkup一个最重要的功能,因为这样可以导入和访问现有XML文件。
分析器在SetDoc和Load方法中分析文档,并建立定位数组,这是一个非验证的分析器,这表明它不会依照着DTD和Schema来检查文档的正确性。它只会检查格式的好坏,如果结束标签不匹配开始标签它会产生一个错误,如果没有根元素存在会产生一个错误,或者其它一些不正确的结点样式它也会产生一个错误。
在7.0版本中,分析器使用了新的分析方法,不再使用递归实现。在以前的版本中,使用递归方法实现时,每次在另一个元素中发现一个元素就调用自己,当它到达父结点的结束标签时返回。因为每次嵌套调用都要增加局部变量和参数到堆栈中,但是在堆栈的尺寸是有限的任何平台上,例如掌上操作系统,这时,递归就成为一个问题了。虽然大多数的文件不会大于10个元素的深度(嵌套),也许在栈中只需要500bytes,然而,这仍是一个潜在的危险。新的分析器使用了一个叫NodeStack的小数组,它就像一个栈,但是它在堆里的。
它保持着一直到当前深度的所有被嵌套的元素的标签名。
分析函数x_ParseElem实际上相当简单,它通过调用x_ParseNode查找和检查下一个结点来循环所有的结点,直到文档的结束。关于结点类型的讨论可以看在Nodes的文章。元素的结束标签被表示成0的结点类型,因为它是元素结点的结束。
NodeStack用来记录被嵌套的元素,当一个起始标签被遇到时,它将呆在NodeStack中,直到相对应的结束标签被发现。如果一个元素A包含其它一些元素,那么这些元素将会放到NodeStack中,并且置于元素A的上面,在恢复父元素(元素A)之前,需要匹配它们的结束标签然后从NodeStack中删除。
x_ParseNode函数是根据在文档中给定结点起始字符的偏移量来识别一个结点。出于对速度和简单考虑,结点被分析只是从第一个字符前进做一个简单的循环,在结点的类型被确定前,保持一个比特标志状态。
如果第一字符是小于号“<”,它就是一个标签“<..>”(对于文本和空格),如果是标签,检查第二个字符,它可能是元素标签名的起始、或者一个斜线(结束标签)、或者是一个感叹号(是注释、CDATA段或DOCTYPE)或是问号(PI),如果是感叹号,你必须要得到下一个字符来判断结点的类型。一旦知道了结点类型,分析器就可以正确扫描这种结点类型相应的结束字符串。
如果第一个字符不是小于号“<”,同时也不空格,那么它是一个文本结点,并且这个结点一直要到一个小于号”<”或者到文档的结束。如果第一个字符是一个空格,那么它也要一直到小于号”<”或文档结束,但是当看到第一个非空格就表明它是一个文本结点。如果到结点的结束都没有遇到非空格,那么,它就是一个空格结点。
现在,让我们这样的步骤来处理下面的简单XML文档。
<?xml version="1.0"?><test> hello world </test>
首先调用x_ParseNode,开始字符是文档的第一个字符,如果是小于号,表明是标签,所以OPENTAG位被设置,下一个字符是一个问号,那么在OPENTAG状态中,表明这是一个处理指令(PI),现在知道了结点类型,不需要设置OPENTAG位了。它会扫描PI的结束字符串 ?>,再返回。
第二步,调用x_ParseNode,开始字符是测试元素的第一个字符,是小于号,表明这是一个标签,所以设置OPENTAG位,下一个字符是一个有效字符,是元素标签名的第一个字符,那么在OPENTAG状态中,意味着这是一个文本结点。现在知道了结点类型,就不需要再设置OPENTAG位了。它会扫描到元素标签的结束字符”>”,接着返回。
第三步,调用x_ParseNode,起始字符是“ Hello World”的第一个字符,这不是小于号了,它是一个空格,所以TEXTORWS (文本或空格)位要被设置。下一个字符是文本字符,所以在EXTORWS 状态中,就意味着这是一个文本结点,现在,解析器知道了结点类型,就不用再设置 TEXTORWS 位了,它会扫描小于号或文档的结束,然后返回。
第四步,调用x_ParseNode,,开始字符是测试元素结束标签的第一个字符,这是一个小于号,表明它是标签,所以要设置OPENTAG位,下一个字符是斜线,在OPENTAG状态中,这意味着它是一个结束标签,现在知道标签的类型了,不用设置OPENTAG位了,它会扫描结束标签的结束字符“>”,然后返回。