从零开始写一个XML解析-02思路

本文介绍了一个名为JXML的自定义XML解析器的构建过程,重点讲解了如何从XML文件读取数据、解析内容并构建XML树形结构。通过递归方式建立节点,实现了XML文件的有效解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 前言

因为我姓贾,所以我的XML就叫JXML

我自己写代码一般都喜欢先写一个大概的思路出来,一般写在txt上。然后思考下大概的可能实现性。然后才开始动手写代码。


github

1. 主逻辑

这里将我们的主逻辑明确一点。

  1. 我们需要一个管理类,来管理从xml文件中读取到的内容以及XML树——JXMLDocument。
    1. 调用JXMLDocument::LoadFile将文件中的内容读取到内存。
  2. 然后开始解析。 我们通过一个主循环,遍历xml文件中的每一行内容。
    1. 根据标签<>判断是什么节点,(当前我们只存在Declaration和Element俩种节点。而且假设xml文件一定是写对的),并且创建这个节点
    2. 将节点的值和属性 记录上去
    3. 将节点插入到树中。

我们的文件结构很明显了

src/
	JXMLDocument.hh
	JXMLDocument.cc
	JXMLNode.hh
	JXMLNode.cc
	JXMLDeclaration.hh
	JXMLDeclaration.cc
	JXMLElement.hh
	JXMLElement.cc

JXMLNode是Declaration和Element的基类。

2. JXMLDocument

2.1 读取文件

void 
JXMLDocument::LoadFile(const string&filename)
{
  ...
  FILE *fp = ::fopen(filename.c_str(), "a");
  ON_SCOPE_EXIT ([&]() { ::fclose(fp); });

  ...
  charBuffer_ = new char[fileLength + 1];
  int nread = 0;
  while(nread != fileLength)
  {
    int n = fread(charBuffer_ + nread, 1, fileLength, fp);
    nread += n;
  }
  ...

  // 对xml文件进行解析
  parse();
}

这里就是简单的一个读取文件内容而已,逻辑很简单。
ON_SCOPE_EXIT是RAII手法,想要了解可以点这里

2.2 解析

void
JXMLDocument::parse()
{
	while()
	{
		JXMLNode *node = parseLine();
		// 识别节点,并做对应处理
		detectNodeType();
		// 将节点插入到XML树上
		insertNodeInXML(node);
	}
}

到这里,其实XML中建立XML树这部分的思路就是这样了。剩下的就是如何解析字符串和如何建立树的问题了。解析字符串我懒得写了。

3. 如何建立XML树

3.1 从测试出发

我自己以前写程序就是直接上手去写,最近写这个自己的JXML才体会到了从测试出发的重要性。
xml文件如下,目前按照最简单的情况思考,没有去考虑Text和属性的解析。给定这么一个xml文件,如何建立一个xml树呢?

<?xml version="1.0"?>  
<scene>  
    <node>  
        <id></id>  
        <name></name>  
    </node>  
    <node2>  
        <id2></id2>  
        <name2></name2>  
        <salary2></salary2>
    </node2>  
</scene>  

理论上建立出来的一个xml树如下图所示

two
one
node2
id2
name2
salary2
node
id
name
scene 根节点

把这个问题抽出来,其实都可以单独拿出来当一个leetcode题。如何建立xml树呢?
建树操作一般都是用递归来做,先考虑递归基的问题。

buildXMLTree(root)
{
	while(parsingToEnd())
	{
		aTag = parse(); //parse()就是解析一个<>这样的标签
		if(isEndTag(aTag)) // 遇到</abc>这样的标签时候,进行return
			return;
		if(isTag(aTag)) // 目前假设一定成功,先不考虑tag不合法的情况
		{
			node = new node(aTag);
			if(!root->last) // 插入称为儿子节点
			{
				root->first = root->last = node;
				node->parent = root;
			}else{
				rootChild = root->last;
				rootChild->next = node;
				node->prev = rootChild;
				root->last = node;
			}
			buildXMLTree(node);
		}
	}
}

简单地走一遍流程。

  1. 第一个标签<scene> ,建立scene节点。然后递归buildXMLTree(scene)。 stack–buildXMLTree(root)
  2. 第二个标签<node>,建立node节点,并将node节点插入成为scene节点的firstChild。 stack–buildXMLTree(scene)
  3. 第三个标签<id>,建立id节点,并将id节点插入成为node节点的firstChild。 stack–buildXMLTree(node)
  4. 第四个标签<\id>,stack–buildXMLTree(id),是endTag,直接返回。
  5. 第五个标签<name>,退栈了,stack–buildXMLTree(node),将name节点插入称为node节点的lastChild,然后和之前的sibling节点建立关系。
  6. 以此类推。

嗯,目前为止这个简单的tinyJXML其实就已经写完了。剩下的就是写代码手法的问题了。我这里列下可以添加的点。

  1. Element和Declaration等节点写成一个Node节点的派生类,利用继承的手法来实现代码复用。
  2. 因为解析的时候,我们可能会遇到建立大量的相同大小的节点,实际上是可以建立一个内存池的。下一篇文章就写《如何写一个内存池》
  3. 使用visitor模式,这个模式主要就是方便别人使用。可以将我们自己的JXML包装成库之后,配合visitor模式就可以让别人方便地使用我们的JXML了。

上一篇

上一篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值