欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

解析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就可以遍历出所有的节点了。上面仅仅是给出了大概的方法,不过我相信要将上面的方法转换成可运行代码,各位都是有这个能力的。。。