解析Html生成标签树(一) HTML算法J#数据结构
程序员文章站
2022-07-12 11:13:48
...
解析Html成标签树结构以后,我们不但可以很容易取得想要的元素,同时也很容易将Html转换成对应的XML文件。但是由于代码是在公司写的,所以没有粘贴出来的可能性,所以我只能给出大概的代码流程,具体细节描述,相信各位都很容易写出来,并且写的比我好,关键的是算法实现思想。算法的关键如下:
1. Html中每个tag都是都将作为树中的一个节点存在的,每个tag都属于树中的某一层。
2. 辅助数据结构:栈(stack)、List、HashTable。其中HashTable[i](i属于int类型)是一个List,用于临时存储第i层子Tag。
3. 顺序扫描Html文本,当遇到”<A~Z”这样的标志,表示可能是一个Tag,调用GetTag()函数对此段代码进行解析,解析出Tag名,Tag属性等等。如果返回值不为空,那么将返回值入栈。并且记录次tag的开始位置。
4. 遇到</A~Z>这样的标志,表示可能是某个Tag的结束。解析出此结束标志的Tag名。如果在栈中找到与此结束标志名同名的元素(此元素属于栈中第iLevel层),那么表示找到匹配的Tag。则Tag出栈,将HashTable[iLevel+1]到HashTable[maxLevel]中的所有元素取出作为此Tag的子节点。放入第HashTable [iLevel]中。并记录Tag的结束位置。
5. 对于<Tag>xxx</Tag>之间的字符串xxx,将其作为特殊的HtmlTextTag处理。出栈,和入栈操作与普通Tag类似。
6. 当栈为空的时候表示最后一次出栈的Tag给根节点。 由于是在公司内部开发的东西,所以不可能把源代码拿出来粘贴,所以只能把大概的代码给出。
伪代码如下:
public void Parse() { char ch = GetCurrentChar(); //取第一个字符 while (!Eof()) { if (ch == '<') { ch = MoveNext(); //取下一个字符 if ((ch >= 'A') && (ch <= 'Z') || (ch == '!')) { iBeginPos = Index; //记录开始位置 //表示可能是一个标签 HtmlTag tag = GetTag(); //解析此Tag if (tag != null) { //首先判断是否有文本 if (m_CurrentText.Lenght > 0) { //将文本作为一个普通Tag入栈 Stack.Push(new HtmlTextTag(m_CurrentText)); } tag.BeginPos = iBeginPos; //记录此Tag的开始位置 Stack.Push(tag); //把Tag入栈 } } ch = GetCurrentChar(); if (ch == '/') { //可能是结束标签 tagName = GetTagName(); //从上到下查看Stack,如果Tag中存在 if (FindInStack(tagName)) { //在栈中找到名为tagName的元素,则把找到的元素出栈 PopTag(tagName); } } } else { //对于<AAA>xxx</AAA>之间的文本xxx,这里将作为TextTag来处理 m_CurrentText.Append(GetCurrentChar()); } //继续处理下一个字符 ch = MoveNext(); } //解析完成以后,如果栈不空,那么把元素出栈,并把最后一次出栈的元素作为根 if (Stack.Count > 0) { HtmlTag tag = null; while (Stack.Count > 0) { tag = Stack.Pop(); PopTag(tag); } //最后一个元素作为根元素 if (tag != null) { m_listRoot.Add(tag); } } } private void PopTag(HtmlTag tag) { int iLevel = Stack.Count; //找到了元素,把iLevel到m_IMaxLevel中所有的元素按照全部作为tag的子元素 for (int i = iLevel + 1; i < m_iMaxLevel; i++) { for (j = 0; j < HashTable[i].Count; j++) { tag.Children.Add(HashTable[i][j]); } } //表示栈已经为空,那么最后一次出栈的tag将作为根 if (Stack.Count == 0) { m_listRoot.Add(tag); } } private void PopTag(string tagName) { /* * 元素出栈的时候,首先需要把当前已经存在了的HtmlTextTag入栈 * 比如:<A>文本段1<B>文本段2</B>文本段3</A> * 在Parse中,当解析出<B>入栈前,需要先把"文本段1"入栈 * 在这里,解析出了</B>结束标志 * 那么首先需要把"文本段2"入栈。 * 解析出</A>则需要把"文本段3'入栈。 * 这样才能够保证"文本段1"和"文本段3"成为<A>的子节点,而"文本段2"作为<B>的子节点 */ if (m_CurrentText.Lenght > 0) { //将文本作为一个普通Tag入栈 Stack.Push(new HtmlTextTag(m_CurrentText)); } HtmlTag tag = Stack.Pop(); //元素出栈 int iLevel = Stack.Count; //记录栈元素数 while (tag.Name != tagName) { //将tag放入第iLevel层的List中 HashTable[iLevel].Add(tag); tag = Stack.Pop(); iLevel = Stack.Count; } //元素出栈后续处理 PopTag(tag); } private HtmlTag GetTag() { if ("如果发现是 < !--开头的元素") //则表示是注释 { SkipComment(); } HtmlTag tag = new HtmlTag(); tag.Name = GetTagName(); //这里的Attribute我将其作为HashTable类型,Hash[属性名]=属性值 tag.Attribute = GetTagAttribute(); return tag; }
解析结束以后,通过访问m_listRoot就可以遍历出所有的节点了。上面仅仅是给出了大概的方法,不过我相信要将上面的方法转换成可运行代码,各位都是有这个能力的。。。
上一篇: 内存溢出
下一篇: 单元测试过程定义研究