非标准的xml解析器的C++实现:一、思考基本数据结构的设计

前言:

我在C++项目中使用xml作为本地简易数据管理,到目前为止有5年时间了,从最初的全文搜索标签首尾,直到目前项目中实际运用的类库细致到已经基本符合w3c标准,我一共写过3次解析器,我自己并没有多喜欢xml,对于xml最初的需求是客户提出的,有了第一次,就有后来的无数次使用xml的场景,配置文件,数据交换,GUI布局,直到现在,新建一个项目,它基本与日志一样成为了一个必备的功能,哪怕在我已经实现了它的替代方案json的当下情况,我依然认为它还有足够顽强的生命力,举个简单例子,用户看json觉得费劲,看不懂,但用户看xml却很容易,客户第一,大家都懂的。所以在跨平台应用中,它的地位依然坚挺。写下这篇文章,表示我正在第4次写xml解析器。

我第三次写的xml解析,除了xml声明里的东西,已经基本符合xml1.0标准了,文档类型定义,命名空间,都按照w3c规范来设计,说起来相当可笑,这些东西,符合标准,实现花了大量时间,但近两年的时间里,一次都没有被我运用到项目中,根本没有实际场景,需要由文档携带逻辑的地方,都已经被我在C++处理清楚了。

在几年前,我首先参考了html,在js中的getElementById,element.innerHTML之类的东西,是必要实现的。

从element.innerHTML来看,如果不做细节处理,这种方式的内存占用随文档深度增加而增加,没有极限。

所以,一个xml节点完整携带它所有子节点文本的方案,是不可取的。

所以当年我思考了一种方案:

dom中只存在一份完整的文档

解析到具体节点的数据,统一使用位置与长度来保存,

需要获取文档数据时,直接从完整文档中根据位置和长度拷贝出来,

这种方式,开始写的时候,是美好的,直到最后写到删,改,索引逻辑的时候,才发现我当时脑子有坑,坑里有翔,翔里有蛆,蛆里下毒@#¥%。

因为最坏的情况,通过dom修改一个文档数据,需要遍历整个文档的所有节点将新的位置与长度改变,但是当时由于项目在赶工,没有充足的时间让我推翻重来,所以只能硬着头皮这么干,好在,用了几年,没出什么大问题,但由于一直没时间,这个问题这几年一直是我最大的心病,没错的,我有强迫症。

好吧,经验教训总结够了,在我开始动手写代码之前,在这里给我的具体实现方案做足功课,同时,给想做,正在做这件事的人提个醒,能够少走弯路。

开始思考吧:结合了经验教训,这次我打算将一个完整的文档分裂成字符串链表来储存在内存里,用一些简单的文档来模拟,应该怎么做。

我这一次不打算实现标准需求里的由文档携带逻辑的部分内容,例如DTD,namespace我打算抛弃掉。

<a>
    <b>1</b>
</a>

上面这个文档,拆分为字符串链表:{"<a>", "<b>", "1", "</b>", "<a>"},在C++中,使用std::list<std::string>来储存。

所以节点数据结构应该设计成:

struct xnode{

    std::list<std::string>::iterator tag_name;//标签名称

    std::list<std::string>::iterator inner_begin, inner_end;//内部文本首尾

    std::list<xnode> childs;//子节点

    xnode *parent;//父节点

    std::list<xnode>::iterator self;//自己在父结点中的迭代位置,它的前后节点,拷贝之后,通过运算符++ --来获取。

};

---------------------------------------------------

xnode root;

解析文档后,

root.tag_name => "<a>"

root.inner_begin => "<b>"

root.inner_end => "</a>"

root.childs.begin() 就是标签<b>的节点,我这里临时用b来表示它。

b.tag_name => "<b>";

b.inner_begin => "1";

b.inner_end => "</b>";

这样子,我需要实现获取innerText的逻辑时,只需要:

std::string str;

for(auto i = elem.inner_begin; i != elem.inner_end; ++i)

    str += *i;

第一步看起来并没有坑点,希望这个方向是正确的,然后使用一个更复杂一点的文档来看看:

<a attr1='1' attr2 = "2">
    <b attr1='1' attr2 = "2">xxx</b>
</a>

涉及到标签属性,情况变得复杂了

首先,把一个标签名称包括属性,如果分裂掉,可能会存在很多1个字符,也由一个std::string来储存的问题。

然后是,解析器的性能也降低了,同时,后续的innerText字符串拼接,也会受到影响。

所以需要诞生另外一个容器,用来储存标签名称,属性?

std::map<std::string, std::list<xnode*>>,可以同时实现记录标签名称,以及根据标签索引实现getElementByTagName这种东西。

属性名称 通常在定义上,等同于常量,重复使用的概率会很大,所以应该是:std::set<std::string> ?

属性值 通常是变量,易变的概率很大,采用跟 属性名称统一的方式好像不是很适合,但属性值好像同样可能出现很多重复的字符串,例如true false之类的。

所以属性值,应该设计为:std::map<std::string, unsigned int> 将val设计为引用计数,为0时,erase掉,emm..不太可能有什么神经病用来解析40亿个节点的文档,所以unsigned int足够了。

所以,思考到这,大致的文档源数据结构出来了:

struct xsource{

    std::list<std::string> docs;

    std::map<std::string, std::list<xnode*>> tags;

    std::set<std::string> attr_names;

    std::map<std::string, unsigned int> attr_values;

};

由此带来的xnode结构的变化之后是:

struct xattr{

    std::set<std::string>::iterator name;

    std::map<std::string, unsigned int> value;

};

struct xnode{

    std::map<std::string, std::list<xnode*>>::iterator tag;

    std::list<xnode*>::iterator itag;//用来在删除标签时,从xsource.docs中删除节点指针。

    std::list<xattr> attrs;

    std::list<std::string>::iterator inner_begin, inner_end;

    std::list<xnode> childs;

    xnode *parent;

    std::list<xnode>::iterator self;

};

今晚就思考到这,我明天先初步按这个思路实现看看。

未完待续...

猜你喜欢

转载自www.cnblogs.com/babypapa/p/11785051.html
今日推荐