非标准的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是一个开源的解 ...
随机推荐
- Noip模拟54 2021.9.16
T1 选择 现在发现好多题目都是隐含的状压,不明面给到数据范围里,之凭借一句话 比如这道题就是按照题目里边给的儿子数量不超过$10$做状压,非常邪门 由于数据范围比较小,怎么暴力就怎么来 从叶子节点向 ...
- 面试官问:说说你对Java函数式编程的理解
常见的面试问题 总结一下,在Java程序员的面试中,经常会被问到类似这样的问题: Java中的函数式接口是什么意思? 注解 @FunctionalInterface 的作用是什么? 实现一个函数式接口 ...
- Spring Cloud Alibaba环境搭建
前言:Spring Cloud Alibaba是目前主流的分布式微服务架构,本文主要讲解了在IDEA中如何搭建Spring Cloud Alibaba环境,以及介绍Spring Cloud Aliba ...
- 计算机网络漫谈之OSI七层模型和TCP/IP四层模型
在 什么是网络? 中,你已经知道计算机网络是物理连接的"局域网"和工作于这个局域网上的"网络协议",并且我们的重心是网络协议.有关网络协议,按照目前的分层方式主 ...
- Qt5 C++ GUI界面 开发环境配置 详细教程
本博客已暂停更新,需要请转新博客http://www.whbwiki.com/333.html Qt 下载 Qt 体积很大,有 1GB~3GB,官方下载通道非常慢,相信很多读者会崩溃,所以建议大家使用 ...
- linux堡垒机下定位日志文件内容
查找关键词grep 命令: grep '关键字' 文件 --color 功能:搜素文件内容 语法: grep [-iv] 关键字 文件 -i 不区分大小写 -v 忽略指定字符串 -n 显示行号 -C ...
- Springboot 启动分析
启动类 Spring 启动类主要是 Annotation (@SpringBootApplication) 和 main 方法体中的 SpringApplication.run. 其中注解 @Spri ...
- mysql: 看不见的空符号 char(9) char(10) char(13)
在统计年度销售额时,总觉得哪里不对劲.于是找了找,对了对,试了trim,消除前后的空格,也没反应. 在崩溃的边缘,终于发现了错的原因. 原来我在录入的时候,粘贴多了其他空白符号,看不见,摸不着,啊~ ...
- LOTO示波器实测——光照强度传感器
loto最近推出了很多的周边传感器模块的实测案例,本文介绍和演示LOTO示波器实测光照强度传感器的使用. 下图就是主角感光模块,可以用来测量光照强度. 这个模块也很简单,只有3个引脚,一个电源,3.3 ...
- Hadoop整体概述
目录 前言 core-site.xml hdfs-site.xml mapred-site.xml yarn-site.xml 一.HDFS HDFS的设计理念 HDFS的缺点 1.NameNode ...