非标准的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;
};
今晚就思考到这,我明天先初步按这个思路实现看看。
未完待续...
非标准的xml解析器的C++实现:一、思考基本数据结构的设计的更多相关文章
- 非标准的xml解析器的C++实现:三、解析器的初步实现
如同我之前的一篇文章说的那样,我没有支持DTD与命名空间, 当前实现出来的解析器,只能与xmlhttp对比,因为chrome浏览器解析大文档有bug,至于其他人实现的,我就不一一测试了,既然都决定自己 ...
- 非标准的xml解析器的C++实现:二、解析器的基本构造:语法表
解析器的目的:一次从头到尾的文本遍历,文本数据 转换为 xml节点数据. 这其实是全世界所有编程语言编译或者转换为虚拟代码的基础,学会这种方法,发明一种编程语言其实只是时间问题,当然了,时间也是世界上 ...
- 4种XML解析器
<?xml version="1.0" encoding="UTF-8"?> <Result> <VALUE> <NO ...
- XML解析器(转)
常见C/C++ XML解析器有tinyxml.XERCES.squashxml.xmlite.pugxml.libxml等等,这些解析器有些是支持多语言的,有些只是单纯C/C++的.如果你是第一次接触 ...
- Java XML解析器
使用Apache Xerces解析XML文档 一.技术概述 在用Java解析XML时候,一般都使用现成XML解析器来完成,自己编码解析是一件很棘手的问题,对程序员要求很高,一般也没有专业厂商或者开源组 ...
- Duilib源码分析(三)XML解析器—CMarkup
上一节介绍了控件构造器CDialogBuilder,接下来将分析其XML解析器CMarkup: CMarkup:xml解析器,目前内置支持三种编码格式:UTF8.UNICODE.ASNI,默认为UTF ...
- tinyxml一个优秀的C++ XML解析器
读取和设置xml配置文件是最常用的操作,试用了几个C++的XML解析器,个人感觉TinyXML是使用起来最舒服的,因为它的API接口和Java的十分类似,面向对象性很好. TinyXML是一个开源的解 ...
- TinyXML:一个优秀的C++ XML解析器
//-------------------------------------------------------------------------------------------------- ...
- 转:TinyXM--优秀的C++ XML解析器
读取和设置xml配置文件是最常用的操作,试用了几个C++的XML解析器,个人感觉TinyXML是使用起来最舒服的,因为它的API接口和Java的十分类似,面向对象性很好. TinyXML是一个开源的解 ...
随机推荐
- CODING 助力江苏高速信息实现组织敏捷与研发敏捷,领跑智慧交通新基建
疫情之下的高速公路管控重任 江苏高速公路信息工程有限公司(以下简称:江苏高速信息)成立于 2002 年,是江苏交通控股旗下,专业从事高速公路领域机电系统集成.智能交通软硬件研发.大数据分析运营的高新技 ...
- Noip模拟81 2021.10.20
T1 语言 比较简单的题,然后就瞎写了,所以考场上就我一个写了线段树的,所以我的常数.... 所以就枚举动词的位置,找前面后面有没有出现$4$即可 1 #include<bits/stdc++. ...
- BOOST内存管理-intrusive_ptr
参考链接https://blog.csdn.net/harbinzju/article/details/6754646 intrusive_ptr 是shared_ptr的插入式版本.与shared_ ...
- 第33篇-方法调用指令之invokeinterface
invokevirtual字节码指令的模板定义如下: def(Bytecodes::_invokeinterface , ubcp|disp|clvm|____, vtos, vtos, invoke ...
- C/C++如何传递二维数组?
用二维数组作为参数传递(用二维数组处理矩阵),但是希望接受传递二维数组参数的函数可以处理任意维度的数组(希望矩阵的行数和列数都是不固定的). ----------------------------- ...
- Android WebView 实现文件选择、拍照、录制视频、录音
原文地址:Android WebView 实现文件选择.拍照.录制视频.录音 | Stars-One的杂货小窝 Android中的WebView如果不进行相应的设置,H5页面的上传按钮是无法触发And ...
- LeetCode-40. 组合总和 II C++(回溯法)
回溯法本身是种暴力解法,虽然效率之类的比较低,但是写起来比较易懂和快.在提交之后的排名也挺低的,大概就超过8%左右.以后复习的时候再去看看题解,看看更高性能的算法.这里先暂时贴上回溯法的代码. 最后说 ...
- WPF进阶技巧和实战09-事件(1-路由事件、鼠标键盘输入)
理解路由事件 当有意义的事情发生时,有对象(WPF的元素)发送的用于通知代码的消息,就是事件的核心思想.WPF通过事件路由的概念增强了.NET事件模型.事件由允许源自某个元素的事件由另一个元素引发.例 ...
- 『学了就忘』Linux基础命令 — 38、Linux中光盘的挂载
目录 步骤一:创建一个空目录 步骤二:找到光盘的设备文件名称 步骤三:挂载光盘 步骤四:访问关盘中的数据 步骤五:卸载挂载点 问题:挂载点为什么要使用空目录 提示:关于Linux系统中光盘的挂载,我们 ...
- 史上最全的Excel导入导出之easyexcel
喝水不忘挖井人,感谢阿里巴巴项目组提供了easyexcel工具类,github地址:https://github.com/alibaba/easyexcel 文章目录 环境搭建 读取excel文件 小 ...