前言:

我在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++实现:一、思考基本数据结构的设计的更多相关文章

  1. 非标准的xml解析器的C++实现:三、解析器的初步实现

    如同我之前的一篇文章说的那样,我没有支持DTD与命名空间, 当前实现出来的解析器,只能与xmlhttp对比,因为chrome浏览器解析大文档有bug,至于其他人实现的,我就不一一测试了,既然都决定自己 ...

  2. 非标准的xml解析器的C++实现:二、解析器的基本构造:语法表

    解析器的目的:一次从头到尾的文本遍历,文本数据 转换为 xml节点数据. 这其实是全世界所有编程语言编译或者转换为虚拟代码的基础,学会这种方法,发明一种编程语言其实只是时间问题,当然了,时间也是世界上 ...

  3. 4种XML解析器

    <?xml version="1.0" encoding="UTF-8"?> <Result> <VALUE> <NO ...

  4. XML解析器(转)

    常见C/C++ XML解析器有tinyxml.XERCES.squashxml.xmlite.pugxml.libxml等等,这些解析器有些是支持多语言的,有些只是单纯C/C++的.如果你是第一次接触 ...

  5. Java XML解析器

    使用Apache Xerces解析XML文档 一.技术概述 在用Java解析XML时候,一般都使用现成XML解析器来完成,自己编码解析是一件很棘手的问题,对程序员要求很高,一般也没有专业厂商或者开源组 ...

  6. Duilib源码分析(三)XML解析器—CMarkup

    上一节介绍了控件构造器CDialogBuilder,接下来将分析其XML解析器CMarkup: CMarkup:xml解析器,目前内置支持三种编码格式:UTF8.UNICODE.ASNI,默认为UTF ...

  7. tinyxml一个优秀的C++ XML解析器

    读取和设置xml配置文件是最常用的操作,试用了几个C++的XML解析器,个人感觉TinyXML是使用起来最舒服的,因为它的API接口和Java的十分类似,面向对象性很好. TinyXML是一个开源的解 ...

  8. TinyXML:一个优秀的C++ XML解析器

    //-------------------------------------------------------------------------------------------------- ...

  9. 转:TinyXM--优秀的C++ XML解析器

    读取和设置xml配置文件是最常用的操作,试用了几个C++的XML解析器,个人感觉TinyXML是使用起来最舒服的,因为它的API接口和Java的十分类似,面向对象性很好. TinyXML是一个开源的解 ...

随机推荐

  1. Noip模拟54 2021.9.16

    T1 选择 现在发现好多题目都是隐含的状压,不明面给到数据范围里,之凭借一句话 比如这道题就是按照题目里边给的儿子数量不超过$10$做状压,非常邪门 由于数据范围比较小,怎么暴力就怎么来 从叶子节点向 ...

  2. 面试官问:说说你对Java函数式编程的理解

    常见的面试问题 总结一下,在Java程序员的面试中,经常会被问到类似这样的问题: Java中的函数式接口是什么意思? 注解 @FunctionalInterface 的作用是什么? 实现一个函数式接口 ...

  3. Spring Cloud Alibaba环境搭建

    前言:Spring Cloud Alibaba是目前主流的分布式微服务架构,本文主要讲解了在IDEA中如何搭建Spring Cloud Alibaba环境,以及介绍Spring Cloud Aliba ...

  4. 计算机网络漫谈之OSI七层模型和TCP/IP四层模型

    在 什么是网络? 中,你已经知道计算机网络是物理连接的"局域网"和工作于这个局域网上的"网络协议",并且我们的重心是网络协议.有关网络协议,按照目前的分层方式主 ...

  5. Qt5 C++ GUI界面 开发环境配置 详细教程

    本博客已暂停更新,需要请转新博客http://www.whbwiki.com/333.html Qt 下载 Qt 体积很大,有 1GB~3GB,官方下载通道非常慢,相信很多读者会崩溃,所以建议大家使用 ...

  6. linux堡垒机下定位日志文件内容

    查找关键词grep 命令: grep '关键字' 文件 --color 功能:搜素文件内容 语法: grep [-iv] 关键字 文件 -i 不区分大小写 -v 忽略指定字符串 -n 显示行号 -C ...

  7. Springboot 启动分析

    启动类 Spring 启动类主要是 Annotation (@SpringBootApplication) 和 main 方法体中的 SpringApplication.run. 其中注解 @Spri ...

  8. mysql: 看不见的空符号 char(9) char(10) char(13)

    在统计年度销售额时,总觉得哪里不对劲.于是找了找,对了对,试了trim,消除前后的空格,也没反应. 在崩溃的边缘,终于发现了错的原因. 原来我在录入的时候,粘贴多了其他空白符号,看不见,摸不着,啊~ ...

  9. LOTO示波器实测——光照强度传感器

    loto最近推出了很多的周边传感器模块的实测案例,本文介绍和演示LOTO示波器实测光照强度传感器的使用. 下图就是主角感光模块,可以用来测量光照强度. 这个模块也很简单,只有3个引脚,一个电源,3.3 ...

  10. Hadoop整体概述

    目录 前言 core-site.xml hdfs-site.xml mapred-site.xml yarn-site.xml 一.HDFS HDFS的设计理念 HDFS的缺点 1.NameNode ...