HTML 目录生成器






4.82/5 (16投票s)
2001年11月4日
12分钟阅读

182755

3699
一个 C# 程序,它接受一个 HTML 文件作为输入,并输出一个嵌入了目录的新文件。

目录
引言
使用 Code Project 已经一年多了(至少感觉是这样),我发现这些文章非常有用。然而,许多较长的文章(不仅是本网站,其他网站也是如此)普遍存在一个问题,那就是有时很难在其中进行导航。我自己的文章中也有这种情况。尽管文章结构良好,但要一眼看出标题是什么并跳转到文档的特定部分并不容易。在 Word 中,我使用“文档结构图”功能来实现此功能,但在 Internet Explorer 中没有这样的工具。
此程序旨在提供一种简单的方法来生成 HTML 文件中的目录,以便用于导航文章。在考虑这个问题时,我意识到程序需要相当简单但又可适应,以便生成不同样式的目录页。这可能很重要,因为虽然项目符号列表在一种样式中可能适用,但在另一种样式中可能不适用。此外,用户可能希望提供自己的标签来更改目录列表中各项的字体等。除此之外,任何专有文本(即非从文章中提取的文本)都应该是可定制的。这主要是为了本地化——万维网是一个全球社区,因此应该能够适应以满足多个文化和语言的需求。
我编写这个小程序还有一个原因,那就是给我的新 .NET Framework 提供一个实际问题来解决。我用 Visual C#.NET Beta 2 编写了此问题的解决方案。下面我将概述程序的使用方法,然后解释我如何解决程序中的一些主要问题,以及 .NET Framework 中用于简化任务的一些功能。
用法
此程序使用起来非常方便。
准备要处理的 HTML 文件。
这是一个非常简单的步骤,只需在 HTML 中您希望出现目录的位置添加一行即可。
<!-- INSERT contents -->
此注释*必须*单独占一行,否则程序将无法检测到它。这一点可以很容易地解决,但在编写程序时,我决定最好将目录部分与其余文本分开。请注意,您必须完全复制此行 - 它是区分大小写的。
我建议您保留此文件的副本以供工作,这样您就不必删除目录并替换上面的标签。如果您将来需要再次编辑它,只需编辑原始文件并重新生成目录即可。
创建目录
同样,这是一个简单的过程。只需运行程序,然后在“要处理的文件”编辑框中输入您希望处理的文件路径,或使用“浏览”按钮。在“输出文件”编辑框中指定输出文件的文件名——即,放置了目录的文件的位置。您不应为输入和输出使用相同的文件名。该系统就是这样设计的,以便在发生错误时不会丢失原始 HTML。
当您单击“确定”时,您将在 TreeView 控件中看到文件概览。如果这是正确的(除非您的 HTML 不符合以下规则,否则它将是正确的),那么再次单击“确定”将在指定位置插入目录并创建输出文件。
局限性和待办事项
由于这最初只是我自己的一个工具,也是学习 .NET Framework 和 C# 一些基础知识的一种方式,因此它仍然存在一些局限性。
-
在某些区域可以做得更好的错误检查。
-
<!-- INSERT contents -->
标签必须单独占一行。 -
标题标签可以与其他内容在同一行,但它们不能溢出到第二行,因为程序将无法识别闭合的 </hN> 标签。
-
标题标签必须被闭合。
<h2>Whatever next?</h2><p>Some text</p> <!-- this is ok --> <h2>Whatever next?<p>Some text</p> <!-- this isn't -->
无论如何,这都是正确的 HTML,无论您是否打算使用此程序,都应该遵守它。
-
代码将包含所有标题级别,但可以轻松修改以停止在例如
<h4>
。可以指示代码忽略某个级别之前的标题级别。这对于 Code Project 文章很有用,因为主要标题实际上是 <h2>。 -
如果您有以下内容,标题将不会被正确标记为 <a name="xyz32"> 标签,尽管目录条目仍然会完整保留。
<!-- <h1>Heading 1</h1> --> <h1>Heading 1</h1>
我计划以后在代码中添加的一个功能是能够从命令行或批处理文件静默运行。这相对容易实现,在某些情况下可能很有用。所有选项都可以从命令行指定,而未指定的选项将使用默认值。如果省略输入和输出文件名,则会发生错误。
工作原理
好的,您知道了如何使用它,下面是它在后台实际工作的细节。程序中使用了几个有趣的 .NET 功能,主要是 Windows Forms 和正则表达式类。下面我将介绍程序的总体运行情况,然后重点介绍我如何使用框架的这两个功能来帮助编写程序。
总体运行
程序首先使用 Windows Forms 窗体从用户那里获取输入和输出文件名。这非常直接,除了可能有一个与为通用对话框添加过滤器相关的问题。实际上,它与在纯 API 中进行操作非常相似,但由于对话框类的 Filters 属性是一个集合,我最初尝试单独添加这两个过滤器。实际上,这不起作用,以下更显而易见的代码就可以了。
fd.Filter = "HTML files|*.html;*.htm|All files (*.*)|*.*";
然后,它使用我的 HtmlContentsBuilder 类来生成文件中文档的树。我使用了一个自定义树,并且没有费心实现集合接口,尽管可能有一种更好的管理树的方法。
BuildTree()
函数逐行遍历 HTML。它首先检查该行是否是上一行延续过来的注释。如果是,它会查找注释的结尾。如果注释的结尾不在这一行,它将丢弃该行,并用下一行回到循环的开头。
然后它会删除该行中的任何注释,如果该行仍处于注释中(将延续到下一行),它会设置一个标志,表明下一行的开头仍然是注释,丢弃该行,并用下一行回到循环的开头。
最后,它会搜索标题标签。如果找到一个,它会搜索闭合标签。这是使用下面描述的正则表达式完成的。
找到标题标签后,会检查其级别。如果它与前一个标题处于同一级别,它将被添加到前一个树分支的右侧。如果它是一个不太重要的标题(例如,<h3> 不如 <h2> 重要),则将其添加为前一个分支的子节点。如果它是一个更重要的标题,则将其添加到前一个标题的正确父节点的右侧。新添加的“叶子”然后被记住为下一次迭代的“前一个”标题。
然后使用生成的树填充树视图。如果用户决定生成内容 HTML,则使用以下过程。
-
选择应出现在文件中的第一个标题。
-
目录的 HTML 通过遍历树来生成。
-
读取文件的一行。
-
如果该行确实包含当前选定的标题,则用新版本的标题替换它,该版本包含小写 H 以符合 XHTML 标准,并包含一个
<a name="xyz32">
标签。请注意,名称标签的末尾会添加一个递增的数字。这是为了处理同一标题出现多次的情况。 -
如果进行了替换,则选择下一个逻辑标题,程序返回到步骤三。
-
当该行完全处理完毕后,会搜索
<!-- INSERT contents -->
标签。这些标签被替换为先前已构建好的目录。这在此阶段完成是为了将来扩展;目前此标签必须单独占一行。 -
完全处理过的行然后保存到输出文件。
-
检索下一行,重复步骤四至七,直到到达文件末尾。
请注意,程序使用 StreamReader
和 StreamWriter
类来输入和输出到 File
对象。事后看来,可以完全省略 File
对象,但这细节并不重要,通过使用它们,我们可以获得更精细地控制。
Windows Forms
对于其整个用户界面,该程序使用 Windows Forms 和通用对话框。我之前已经演示了通用对话框的一个小编程陷阱。我将简要提及在使用 Windows Forms 创建 UI 时需要考虑的几个重要点。
首先是确定用户在对话框关闭后单击了“确定”还是“取消”。这似乎非常明显,但由于不熟悉 Framework,我没有立即注意到。与之前一样,结果是从 ShowDialog
函数的返回值确定的。然而,问题在于如何首先设置这个结果。要做到这一点,当用户单击“确定”时,在关闭之前,您应该在您的 OK-clicked 事件处理程序中将 DialogResult
属性设置为 DialogResult.OK
。
private void btnOK_Click(object sender, System.EventArgs e) { DialogResult = DialogResult.OK; Close(); }
Windows Forms 的另一个问题是对话框的重排。我不得不说,使用新的类库,这做得非常出色。只需几秒钟即可为您的窗体添加重排功能。您只需要为窗体上的控件设置 Anchor 属性。
如果一个控件在一侧被锚定,那么即使在重排后,该侧距离窗体的边缘也始终保持相同的距离。如果未锚定,则其相对于屏幕侧面的位置不会改变。以下是一些示例:

这将导致控件的底边固定在窗体的底边,右边固定在窗体的右边。因此,控件的大小将保持不变,但会固定在窗体的右下角。这种类型的重排用于本示例应用程序主对话框的右下角的按钮。

这将导致控件的所有边缘固定在窗体的相应边缘。这将导致控件在窗体被重排的任何方向上“拉伸”。这用于本示例主对话框中的 TreeView 控件。
正则表达式
你们中的许多程序员可能熟悉正则表达式的概念。基本上,它们是一种用于模式匹配的查找和替换工具,类似于 DOS 的通配符功能。
.NET Framework 提供了一个命名空间,其中包含几个与正则表达式相关的类,为我们在程序中使用它们提供了所有必要的工具。它们在此程序中用于识别标题标签和注释非常有用。
有两种使用正则表达式的选项:创建 RegEx
对象,或使用静态方法。无论哪种方式都可以,但如果您多次使用搜索模式,则使用前一种方法可能会获得更好的性能,因为它只需要编译一次。我的意思是,它只需要从您输入的表单,例如 "<h[1-8]>"
编译成一次内部使用的操作码。
正则表达式类可以用作简单的字符串搜索工具,或者可以使用可以插入字符串以表示某些“通配符”的附加代码。以下是我使用的:
代码 | 含义 |
[XYZ] | 在此位置可以出现 X、Y 或 Z。 |
[A-F] | 此位置可以出现 A 和 F 之间的任何字符。 |
\w | 此处可以出现任何单词字符。 |
\W | 此处可以出现任何非单词字符。 |
* | 零次或多次。 |
+ | 一次或多次。 |
请注意,至少在 C++ 和 C# 中,您必须使用双反斜杠(\\)才能在字符串中出现单个反斜杠。或者,在 C# 中,您可以使用 @' 符号前缀字符串。
"Hello \/ World" // error because \/ isn't a recognised escape sequence @"Hello \/ World" // = "Hello \/ World", because of the usage of the @ sign
结论
我认为我已经涵盖了所需的一切。希望您发现这是一个有用的工具——本文档顶部的目录就是使用它自动生成的。代码注释相当详细,但我并不声称完美,因为我写得很快。据我所知,除了文章开头列出的一些小限制外,没有其他错误,我认为这些限制大多是合理的。我现在很忙,但希望能够抽出时间更新文章并为命令行运行提供工具支持。如果您有任何意见或建议,请随时告知我。
更新
- 代码已修改,通过删除链接标签中的空格来生成更美观的输出。
- 修复了一个错误,程序使用了一个正则表达式,其部分内容来自文件中的标题文本。然而,程序未能修饰作为正则表达式语法一部分的字符。有关更多详细信息,请参阅 RXDecorate 函数。