前言:

我在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. linux基本命令二

    组管理与权限管理  文件/目录所有者  修改文件所有者 chown 用户名 文件名 创建文件所在组  groupadd 修改文件所在组 chgrp  组名  文件名 其他组:除文件的所有者和所在组的用 ...

  2. docker commit 制作自定义tomcat镜像

    官网下载的tomcat一般存在漏洞,在生产环境中一般是自己下载jdk和tomcat制作成一个新的镜像使用 1.将jdk和tomcat上传 2.生成 c3 容器 3.将jdk和tomcat拷贝到c3容器 ...

  3. 启用MFA的office 365 账号如何连接Exchange online

    第一篇随手笔记,从简单开始... 如何使用Exchange Online PowerShell呢? 以Windows操作系统为例,如Windows10:首先需要安装Exchange Online Po ...

  4. Redis 客户端重试指南

    本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可. 在互联网服务中,特别是在云环境下,网络及硬件环境复杂,所有应用程序都可能遇到暂时性故障.暂时性故障包括瞬时的网络抖动,服务暂时不可 ...

  5. jenkins 安装与使用

    1.jenkins下载:https://jenkins.io/zh/download/ 2.将下载好的war包放到tomcat容器下的D:\apache-tomcat-9.0.10\webapps下( ...

  6. 大白话讲解如何解决HttpServletRequest的请求参数只能读取一次的问题

    大家在开发过程中,可能会遇到对请求参数做下处理的场景,比如读取上送的参数中看调用方上送的系统编号是否是白名单里面的(更多的会用request中获取IP地址判断).需要对请求方上送的参数进行大小写转换或 ...

  7. robot framework 常用关键字介绍

    1.log 打印所有内容 log hello word 2.定义变量 ${a} Set variable 92 log ${a}   3.连接对象 ${a} Catenate hello word l ...

  8. 聊一聊声明式接口调用与Nacos的结合使用

    背景 对于公司内部的 API 接口,在引入注册中心之后,免不了会用上服务发现这个东西. 现在比较流行的接口调用方式应该是基于声明式接口的调用,它使得开发变得更加简化和快捷. .NET 在声明式接口调用 ...

  9. celery tasks always in pending

    Result backend doesn't work or tasks are always in PENDING state¶All tasks are PENDING by default, s ...

  10. ORACLE,mysql中替换like的函数

    数据库中存储了海量的数据,当查询时使用like,速度明显变慢.我在做项目时,发现使用内部函数INSTR,代替传统的LIKE方式查询,并且速度更快. INSTR()函数返回字符串中子字符串第一次出现的位 ...