65.9K
CodeProject 正在变化。 阅读更多。
Home

另一个 C# 旧版 HTML 解析器, 使用标签处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (18投票s)

2008年2月23日

CPOL

5分钟阅读

viewsIcon

141811

downloadIcon

6061

用于 HTML 标签工作的 HTML 解析器的类库。

screenshot03.gif

引言

本文介绍了我为自动化应用程序开发的简单 HTML 解析器库。该解析器主要检测标签语法,并将一对标签收集为一个组。我曾尝试使用 ANTLR 等解析器生成器,但时间紧迫,没有时间学习其语法,因此最终自己编写了它。

该解析器旨在与 .NET WebResponse 类检索的 HTML 内容一起使用。因此,我还开发了一个名为 NativeWebSurf 的工具,它通过 WebResponse 下载 HTML 内容,并使用我的解析器将其解析为 HTML 结构。

Using the Code

该库和工具是用 .NET 3.5 和 Visual Studio 2008 编写的。现在有两个存档文件:NativeWebSurfNativeWebSurf_1.0.1。前者没有大量使用 LINQ 或扩展方法,但在后者中我更多地应用了 LINQ。(我最终发现它是一个相当方便的语言特性。如果有人想将其转换为 .NET 2.0,请尝试前者存档。

NativeWebSurf 解决方案包含三个项目。

  • NativeWebSurf:这是使用 RZLib 解析器的主要应用程序。
  • RZLib:这是一个包含解析器源代码的类库。该项目使用了 C5 Generic Collection library,但未提供。请从提供的链接下载。
  • RZLib.UnitTestRZLib 的单元测试模块。它使用 NUnit 2.4.3

解析器类是 RZ.Web.HtmlParser。要创建解析器对象,请将 HTML 文本传递给其构造函数。

using RZ.Web;

namespace TestLib
{
    class Program
    {
        HtmlParser parser = new HtmlParser(
            "<html><body>any HTML/text here...</body></html>");
    }
} 

HtmlParser.CurrentContent 表示解析器刚刚读取的内容对象。内容对象由内容类表示,这些类以 HtmlContent 开头。内容类的层次结构如下:

  • HtmlContent
    • HtmlContentText
    • HtmlContentTag
      • HtmlContentHeadTag
        • HtmlContentOpenTag
        • HtmlContentCompleteTag
        • HtmlContentBlock
      • HtmlContentCloseTag

粗体类名 是一个 abstract 类。

HtmlContentText 保存所有不被视为标签内容的内容。

HtmlContentOpenTagHtmlContentCloseTag HtmlContentCompleteTag 分别保存开标签、闭标签和完整标签(即 <br />)的信息。

当解析器刚创建时,其 CurrentContent null。所有解析器的 public 方法都会使其更改为一个有效对象。

FetchNextContent()CurrentContent 移动到下一个内容。

MoveToHeadTag()CurrentContent 移动到下一个开/完整标签。

MoveToTag()CurrentContent 移动到指定名称(通过其参数传递)或谓词的开/完整标签。

使用 C# 3 的 lambda 表达式,谓词语句变得更紧凑(与匿名方法相比),现在 MoveToTag() 可以这样使用:

parser.MoveToTag( tag => tag.TagName == "meta" && tag.Attributes["name"] == "Rating" );

GrabCurrentTag() 只能在 CurrentContent 处于开/完整标签时使用。它将匹配结束标签并将整个内容放入 HtmlContentBlock ,后者具有树形结构。

示例

HtmlParser parser = new HtmlParser(

@"<html>
    <head>
        <title>abc</title>
    </head>
    <body>any text here...
    </body>
</html>"
           ); 

Debug.Assert( parser.MoveToTag("head") );  // locate to <head> tag.

Boolean hasCloseTag;  // if it is false, it means parser cannot find its close tag.
HtmlContentBlock headBlock = parser.GrabCurrentTag(out hasCloseTag);

Debug.Assert( hasCloseTag );   // since we have </head>

Debug.Assert( headBlock.TagName == "head" );
Debug.Assert( headBlock.Attributes.Count == 0 );  // no attributes in <head>
Debug.Assert( headBlock.Count == 3 );  // there are 3 contents inside headBlock
Debug.Assert( headBlock[0] is HtmlContentText );  // \r\n between <head> and <title>
Debug.Assert( headBlock[1] is HtmlContentBlock ); // block of <title>
Debug.Assert( headBlock[2] is HtmlContentText );  // \r\n between </title> and </head>

HtmlContentBlock titleBlock = (HtmlContentBlock) headBlock[1];

Debug.Assert( titleBlock.Count == 1 );
Debug.Assert( titleBlock[0].ToString() == "abc" );

Debug.Assert( parser.CurrentContent is HtmlContentCloseTag );  // it is at </head>

当前的解析器是只读的。最好将 HTML 解析为块,以便进行反向读取。

HtmlContentBlock

HtmlContentBlock 的理念是创建一个对象,该对象仅收集两种类型的内容:

  • HtmlContentText
  • HtmlContentBlock

Count 属性可用于确定其中子项(文本或块)的数量,并且我们可以通过索引器获取项。

然而,使用其迭代器(配合 LINQ 或 foreach )可能会更容易。

它提供了两个版本的迭代器:第一个是默认的,用于 HtmlContentBlock;另一个是 HtmlContent。(请注意,其默认迭代器已从 IEnumerable<HtmlContent> 更改为 IEnumerable<HtmlContentBlock>IterateChild() 方法被创建用于 HtmlContent 的枚举——因为我认为人们会比文本更常扫描块)。

另一种搜索标签的方法是通过 FindTag() 方法,该方法也可以按名称或谓词进行查找。它返回一个 Int32 数组作为索引,我们可以使用该索引通过索引器获取内容,并且该索引可以作为下一次查找的起始位置。

问题

脚本支持

此解析器可以处理 JavaScript 标签,但不能处理其他语言。它不理解所有 JavaScript 语法,但它可以识别 JS 字符串和注释,并将所有 Java 代码视为普通文本。

Bug

至少有一种情况可能导致解析器失败,当它失败时,会抛出 HtmlParserException。我知道的情况是无效的标签格式,例如:

This is <b>HTML <i>text</i</b>.

解析器期望一个格式正确的闭标签,但它不是。我在 HtmlLegacyParser.cs 中用 TODO: 标记了代码,可以在此处处理此情况。

一些不相关的问题...

在我开发 NativeWebSurf 期间,我注意到 WebResponse 返回的 Cookie 对象总是包含一个路径,即使 Set-Cookie 响应头没有指定它!?我不知道这是否是框架的 bug……但它可能会造成问题,因为我们永远不知道它是否真的从 Web 服务器返回……嗯,这里可能不是询问的好地方,但如果有人知道什么,我很感激你能分享一下:)。

最后

我希望它也能成为对他人的有用库。如果您修复了某些 bug 或添加了某些功能,也请与我分享。

历史

  • 2008-02-27:更新文章
    添加了一些功能并重构了库。
    • NativeWebSurf
      + 支持 Cookie 缓存(具有同一主机的所有路径)
      + 支持 HTML 内容字符集
      * 修复了代码以支持新的 HtmlContentBlock 迭代器RZLib
    • HtmlContentBlock
      * 将默认迭代器更改为返回 HtmlContentBlock 而不是 HtmlContent
      + 支持 Int32[] 索引的索引器
      + 用于 HtmlContent 枚举的 IterateChild()
      + 支持谓词的 FindTag()
    • HtmlParser
      + 支持谓词的 MoveToTag()
  • 2008-02-24:发布初始文章
© . All rights reserved.