0. 前言
因为我姓贾,所以我的XML就叫JXML
我自己写代码一般都喜欢先写一个大概的思路出来,一般写在txt上。然后思考下大概的可能实现性。然后才开始动手写代码。
1. 主逻辑
这里将我们的主逻辑明确一点。
- 我们需要一个管理类,来管理从xml文件中读取到的内容以及XML树——JXMLDocument。
- 调用JXMLDocument::LoadFile将文件中的内容读取到内存。
- 然后开始解析。 我们通过一个主循环,遍历xml文件中的每一行内容。
- 根据标签<>判断是什么节点,(当前我们只存在Declaration和Element俩种节点。而且假设xml文件一定是写对的),并且创建这个节点
- 将节点的值和属性 记录上去
- 将节点插入到树中。
我们的文件结构很明显了
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树如下图所示
把这个问题抽出来,其实都可以单独拿出来当一个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);
}
}
}
简单地走一遍流程。
- 第一个标签<scene> ,建立scene节点。然后递归buildXMLTree(scene)。 stack–buildXMLTree(root)
- 第二个标签<node>,建立node节点,并将node节点插入成为scene节点的firstChild。 stack–buildXMLTree(scene)
- 第三个标签<id>,建立id节点,并将id节点插入成为node节点的firstChild。 stack–buildXMLTree(node)
- 第四个标签<\id>,stack–buildXMLTree(id),是endTag,直接返回。
- 第五个标签<name>,退栈了,stack–buildXMLTree(node),将name节点插入称为node节点的lastChild,然后和之前的sibling节点建立关系。
- 以此类推。
嗯,目前为止这个简单的tinyJXML其实就已经写完了。剩下的就是写代码手法的问题了。我这里列下可以添加的点。
- Element和Declaration等节点写成一个Node节点的派生类,利用继承的手法来实现代码复用。
- 因为解析的时候,我们可能会遇到建立大量的相同大小的节点,实际上是可以建立一个内存池的。下一篇文章就写《如何写一个内存池》
- 使用visitor模式,这个模式主要就是方便别人使用。可以将我们自己的JXML包装成库之后,配合visitor模式就可以让别人方便地使用我们的JXML了。