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

CleanR - 文本文件字符串搜索和替换引擎。

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.29/5 (8投票s)

2003年7月2日

11分钟阅读

viewsIcon

134393

downloadIcon

3308

一个允许您在文本文件中搜索字符串并替换它们的引擎。

引言

这实际上是我在收到数百封电子邮件后产生的想法,这些邮件来自要求我帮助他们清理感染了 Win32.Redlof.A 的基于 HTML 的脚本的系统的人们,该脚本源自我在同一篇关于基于脚本的病毒的文章 http://neworder.box.sk,题为“关于 Win32.Redlof.A 病毒的论文”(大概是这样)

我着手编写一个小型实用程序,可以查找受感染的文件并删除恶意代码,CleanR 就此诞生。该实用程序的作用是搜索文本(ASCII)文件中的特定字符串出现,并将其替换为另一个字符串,该字符串也可以是 NULL(相当于完全替换搜索字符串)。

因此,为了删除病毒字符串,我将搜索字符串设置为病毒脚本,将替换字符串设置为 NULL。幸好这种方法奏效了,从此大家都过上了幸福快乐的生活。

不久之后,人们也开始使用我这个小工具来移除保存的 HTML 页面中的广告和弹出脚本行,或者纠正他们文本文件中的拼写错误。

我最后一次听说,有人推荐我的实用程序来替换从保存的 Yahoo! 邮件页面中删除以下脚本

<script>
<!-- 
    function Help(link)
    {
    window.open(link,"help",
           "width=400,height=500,scrollbars=yes,dependent=yes");
    }
    if (document.cookie.indexOf("o/Rl.A") == -1) {
    window.open("http://mail.yahoo.com", "_top");
    }
// -->
</script>

通过将上述脚本指定为搜索字符串并将替换字符串保留为 NULL,就可以删除该脚本,他们也能够再次查看保存的邮件!(否则,如果只有上述脚本,就会打开一个指向 http://mail.yahoo.com 的窗口,而不是保存的邮件!) (如果我没记错的话,Yahoo! 从此就停止将上述脚本放入他们的页面中了!)

您现在可以通过以下选项进行操作

  • “仅检查单词”:要求实用程序替换特定的单词而不是子字符串。例如,如果程序遇到字符串“WeAreAllFriends”,而您想将“Friends”替换为“Foes”,它将不会替换该字符串;但如果字符串是“We are all Friends”,那么“Friends”将被替换为“We are all Foes”。

    请注意,默认情况下,所有文本操作都是区分大小写的——默认情况下,“Friends”和“friends”是两个不同的字符串!您只需取消选中“区分大小写”复选框即可进行不区分大小写的搜索。

    注意:在不区分大小写的搜索中,此程序将消耗比区分大小写搜索多一倍的内存。

  • “严格遵守语法”:要求实用程序仅更改具有正确语法的单词。例如,如果搜索字符串是“Friends”,而程序遇到了
    "What kind of Friends!Wish I died!"
    "What kind of Friends;wish I died!"
    "What kind of Friends!-Wish I died!"
    

    等等,它们都会被忽略。在“严格语法”选项下,有效字符串的示例包括

    "What kind of Friends! Wish I died!"
    "What kind of Friends, Wish I died!"
    "What kind of Friends - Wish I died!"
    "What kind of Friends wish I died!"
    

    [新句子开头大写等条件目前不检查]

    由于此选项会自动包含仅检查单词的条件,因此一旦选择此选项,单独的“仅检查单词”选项将禁用。

  • “忽略最后一个单词的标点符号规则”:在“严格语法”选项下,您可以选择“忽略最后一个单词的标点符号”。

    如果最后一个单词与搜索字符串匹配,此选项将不检查该文本文件中最后一个单词的语法有效性。

    例如,如果“Bye”是搜索字符串,并且勾选了上述选项,而“Bye”是文本文件中的最后一个单词,且后面没有正确的标点符号(通常是句号),例如

    如果“With lotsa love - Bye”是文本文件的最后一行,那么“Bye”后面缺少所需的标点符号(句号)这一事实将被忽略,否则如果未启用此选项,它就不会被忽略。

  • “覆盖原始文件”:要求程序用修改后的文件覆盖原始文件。

  • “从文件获取搜索字符串”:勾选此框后,将打开一个文件对话框,您可以在其中选择包含搜索字符串的文本文件。这将禁用搜索字符串编辑框。

  • “从文件获取替换字符串”:勾选此框后,将打开一个文件对话框,您可以在其中选择包含要替换匹配文件中的搜索字符串的文本文件。
    这将禁用替换字符串编辑框。

  • “区分大小写”:默认勾选,因此所有文本操作都区分大小写——默认情况下,“Friends”和“friends”是两个不同的字符串!
    您只需取消选中“区分大小写”复选框即可进行不区分大小写的搜索。

注意:在不区分大小写的搜索中,此程序将消耗比区分大小写搜索多一倍的内存。

在所有选项都关闭的情况下,子字符串也算作单词。使用“浏览”按钮选择您要操作的文件。输入文件名是Test.txt(已打包),处理后的文件名是TestOut.txt

文本文件和 CR/LF(0x0D & 0X0A 对)的组合 - 对本程序的影响

我在此声明,我将要说的是纯粹我个人的观点——国际标准并没有强制要求此观点或将其作为标准。

我想说的是:

“文本文件中的换行符应表示为 CR/LF 对。”

请注意,您可以自由选择是用 LF、CR 还是 CR/LF 来表示换行符。

实际上,我们现在所说的换行符指的是

A Line of text [line break here]
Next line continues from here..

在 DOS 时代,[此处换行] 的序列是“\r\n”,即 0X0D & 0X0A。

而,单独的 LF(‘\n’,即 0x0A)将用于表示

A Line of text [LF here]
           Next line continues from here..

明白了吗?LF 不应该将回车符带入下一段,而只是向下移动一行,并从当前 X 坐标继续!

但如今,由于 CR 和 LF 的独立性未被识别,我们不得不使用几个制表符和/或空格来模拟上面的第二行缩进,而实际上一个 LF 就足够了!

单独的 CR 当时*经常*用于表示一个字母序列的*传输结束*。然而,随着更好的传输信号方法,这很快就成为一种形式,即使在今天,套接字库也将其识别为“\r\n”作为数据或命令的传输结束。

现在,您会问一个显而易见的问题:为什么告诉我这些?嗯,好问题!(这表明您*正*以热情阅读我的文章 ;) 原因如下:

以*二进制模式*传输的文本文件(如 HTML 页面)可能缺少一些字符序列。如果您分别在 Wordpad 和记事本中打开这些文件,就可以识别它们。在记事本中,HTML 会显示得不带格式(没有正确的缩进等),但在 Wordpad 中,它们看起来布局良好。这是因为 HTML 文件是以二进制模式通过互联网传输的,结果,换行符和回车符*未*被纠正,而如果以文本模式传输则会被纠正。这就是为什么 HTML 源文件在记事本中看起来很难看的原因。而 Wordpad 会纠正回车换行符序列,所以 HTML 源文件看起来很正常。

所以,假设您下载了一个 HTML 页面,它在您的浏览器中显示为

First line.
Second line.
而您想让我的程序将上面的文本替换为
No ! I don't want these two lines.
所以您输入
First line.
Second line.
作为搜索字符串到我的程序中,替换字符串为
No ! I don't want these two lines.
然后按 OK。

然后您会发现我的程序“行为不端”,愚蠢地说“抱歉!...未找到搜索字符串!”。

于是您给我发电子邮件,抱怨我是多么#%***@!,以及您多么希望****!(是的!我第一次发布这个实用程序时,我真的收到了 52 封关于这个问题的电子邮件。这就是为什么我更新了我的程序以添加转换例程!)

重要提示:此说明不适用于使用 Internet Explorer 及衍生浏览器保存的网页。IE 会在保存前自动执行所需的转换(但您仍然可以进行实验)。

我的程序“行为不端”的原因是,在二进制传输期间,系统不会修改数据。因此,如果服务器上的新行表示为“\n”,则*仅*保留为“\n”,而不是期望的“\r\n”。

所以,在浏览器中显示为

First line.
Second line.
的内容,实际上可能是
First line.[LF]Second line.
而您输入
First line.[CR/LF]
Second line.
作为搜索字符串!我通过使用十六进制编辑器查看这样的文件来验证了这一点。您也可以这样做。

因此,所需要做的就是将单独的“\n”(而不是现有的“\r\n”)更改为“\r\n”。然后就可以进行处理了,因为您看到“第一行。[LF]第二行。”与“第一行。[CR/LF]第二行。”不同!也就是说,将单独的“0x0A”更改为“0x0D 0x0A”。

当前版本可以处理此类文件,并允许您执行以下操作:

  1. 转换搜索字符串 CR/LF 为 LF:(将搜索字符串的 CR\LF 更改为单独的 LF)这将允许您处理上面提到的文件,其中换行符由单独的 LF 表示,而不是正确的 CR\LF 组合。
  2. 转换文件 LF -> CR\LF:(将要操作的文件的所有 LF 更改为 CR\LF 对)这将重新格式化上述类型的文件,其中所有 LF 都被正确的对替换。然后处理后的文件将格式正确,即使在记事本等简单文本编辑器中也能阅读。

程序性能和局限性

请注意,为了提高程序性能并减少编程错误,整个文本文件都会被读取到 RAM 中。这种方法受到一些人的诟病,但被许多人赞赏,所以我决定坚持下去。

然而,这种方法给程序带来了一个限制——您可以操作的文件大小不仅取决于您拥有的 RAM,还取决于您的操作系统和 MFC 架构允许单个程序进行的最大默认动态内存分配,以及程序堆大小分配(这会因系统和时间而异)。

但就我而言,我没有 100MB 的长文本文件来测试我的程序——我最多尝试了一个 5MB 的 HTML 文件,运行良好。我正在尝试使用 GlobalAllocation 函数,但这需要时间——任何能做到这一点的人,即使是为了其他人使用此程序,也将帮我一个大忙。

我还想提请您注意,尽管我用比任何实际文本文件都复杂的文本测试了该程序,但我可能还是遗漏了某些东西——如果您遇到错误,请立即通知我,提供任何有助于我重现错误的信息,或/以及改进建议。

使用代码

我想提请您注意 ScanR 实用程序,该实用程序也在 CodeProject 上,由本人制作,它已经对这个引擎进行了测试。实际上,ScanR 是一个独特的文本文件字符串搜索和替换实用程序,并且正如我们在其中处理多个文件一样,CleanR 引擎在某些地方进行了优化——您可能会在那里找到您的兴趣!

在我看来,ScanR 目前是展示此类功能的最佳示例。您可以大致了解此引擎的速度,如果您愿意提供帮助,它肯定会变得更好(我知道有错误——但具体是哪些?我正在找出 :))。

您可以直接使用该类而无需进行任何修改(这将有助于跟踪代码中的错误)。

使用该类非常简单,就像这样:

CCleanRboolSet theSet;
    
//.. set member variables of theSet 

CCleanR theR(theSet); //call the parameterized constructor to 
                      // send the options
theR.SetFileName(Put FilePath here ..);
theR.SetReplacementString(Put Replacement String here ..);
theR.SetSearchString(Put search string here ...);
theR.Process();

涉及的步骤是:

  • 声明一个 `CCleanRboolSet` 结构的对象(例如 `theR`),它实际上封装了程序选项。将成员变量初始化为适当的值以反映您的需求。例如,如果您希望搜索区分大小写,请执行以下操作:
    theSet.bCaseSensetive=m_bCaseSensetive;
  • 创建一个 `CCleanR` 对象,并将上述对象传递给构造函数——例如,在这个例子中是:
    CCleanR theR(theSet);
  • 设置我们将要操作的文件名、搜索字符串、替换字符串,例如:
    theR.SetFileName(Put FilePath here ..);
    theR.SetReplacementString(Put Replacement String here ..);
    theR.SetSearchString(Put search string here ...);
    
  • 然后调用 `CCleanR::Process()` 函数进行处理,该函数将返回找到的匹配项的数量。
    theR.Process();
    
  • 您就完成了!

要点(主要对我而言)

在编码此引擎时,我发现 `CString` 确实做了一件好事——它将所有出现的“\n”更改为“\r\n”,这可能会占用更多空间(每替换一个“\n”会多出 2 个字节),但这是处理文本文件的*正确*方法。像记事本这样的纯文本编辑器将 Enter 键存储为纯换行符,而不是回车换行符对,而后者是存储在文本文件中的推荐方式。

正如我之前告诉您的,我将整个文本文件读入内存,这极大地提高了程序性能。我还假设用户不太可能创建 100MB 的文本文件。但是,我真的很想知道会发生什么?到目前为止,即使是 15MB 的文本文件也*没有*引起问题。坦白说,15MB 的文本文件对我来说已经太多了;)

你们中的许多人可能会指出使用 `CreateFileMapping()` 可能有帮助——请发送您的看法。我认为我们主要会在小文本文件上使用此工具,而文件映射会带来太多的开销。这就是我对此犹豫不决的原因。

历史

  • 6 月 14 日 - 公开正式发布。
  • 6 月 23 日 - 添加了 CR/LF 相互转换的代码。
© . All rights reserved.