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

使用 Regex++ 将正则表达式添加到您的应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (17投票s)

2002年6月18日

CPOL

10分钟阅读

viewsIcon

529446

downloadIcon

3103

一篇教程,演示如何使用来自 boost.org 的 Regex++ 为您的项目添加正则表达式。

引言

什么是正则表达式?简而言之,正则表达式提供了一种将原始数据转换为可用数据的简单方法。在《精通正则表达式》(O'Reilly & Associates)的序言中,Jeffrey Friedl 写道:

“正则表达式在如此多样的应用程序中出现是有充分理由的:它们极其强大。从低层次来看,正则表达式描述了一段文本。您可以使用它来验证用户的输入,或者筛选大量数据。从更高层次来看,正则表达式允许您掌控数据。控制它。让它为您工作。精通正则表达式就是精通您的数据。”

您可能不知道,Microsoft Visual Studio 文本搜索工具中就包含正则表达式。它提供了一种非常强大的方法来搜索代码(或任何文本文件)中复杂的模式。如果您以前从未使用过正则表达式,这里有一些关于它的网络链接,可以帮助您入门。

入门

正则表达式虽然看起来难以学习,但却是程序员工具箱中最强大的工具之一,然而许多程序员却从未利用它们。您当然可以编写自己的文本解析器来完成工作,但那样做需要更多时间,更容易出错,而且远没有那么有趣(恕我直言)。

Regex++ 是一个可从 https://boost.ac.cn 获取的正则表达式库。Boost 提供免费的经过同行评审的可移植 C++ 源代码库。访问该网站了解更多信息。出于我们的目的,我们只关注 Regex++,但您可能会发现他们的许多库都很有用。Regex++ 最初作者的网站是 http://ourworld.compuserve.com/homepages/John_Maddock/

安装 Regex++

注意: 以下说明仅在您安装了 Visual Studio 6 或 7 时有效。

要安装 Regex++,请完成以下步骤(详细说明也可在 Regex++ 下载本身中找到)

  1. 从原始作者网站下载 Regex++。这样您将只获得 regex 库,而不是整个 boost 库。
  2. 解压到目录 C:\Regex++(在“解压到:”字段中键入路径 C:\Regex++,如下图所示)
  3. 打开命令提示符
  4. 将目录更改为 C:\Regex++\libs\regex\build。在此目录中,您将找到几个 make 文件。您感兴趣的是 vc6.mak
  5. 为了使用 Visual Studio 的环境设置,您必须运行批处理文件 vcvars32.bat。它应该在您的路径中,所以您不需要指定它的完整路径。只需在命令提示符窗口中键入 vcvars32.bat。
  6. 类型
    nmake -fvc6.mak
    构建需要一点时间。
  7. 类型
    nmake -fvc6.mak install
    (将库和 DLL 安装到适当的位置)
  8. 类型
    nmake -fvc6.mak clean
    (这个可能会出现一些错误。我遇到了,但如果需要,您可以手动删除中间文件)

现在您的库已构建并就位,可以使用了。我上面包含的项目旨在演示如何简单地解析 HTML。您现在需要做的就是打开项目并确保项目设置指向适当的 regex++ 库和包含目录。但首先进行简短讨论

注意: 要将 Regex++ 库添加到您的项目,请选择 Project | Settings...。在随后的对话框中,选择 C/C++ 选项卡。在 Category 下拉列表中,选择 Preprocessor。在 Additional include directories: 编辑框中输入 C:\Regex++。现在选择 Link 选项卡。在 Category 下拉列表中,选择 Input。在 Additional library path: 编辑框中输入 C:\Regex++

解析 HTML

HTML 解析器并非新事物。既然轮子已经发明了,就没有理由(至少我能想到)有人需要编写自己的解析器了。话虽如此,我们将要使用的示例正是这样做的——解析 HTML。我这样做是因为解析 HTML 提供了一个很好的教学示例。具体来说,它解析 HTML 文档中的表单元素。这是一项相当复杂的任务,但是,使用正则表达式可以使其变得简单。我们希望我们的解析器足够通用,能够解析任何给定输入字段中键值对。例如,在 HTML 中

<input type="text" name="address" size=30 maxlength = "100">
我们希望只提供键名(例如 type、name、size 等),并让正则表达式返回该键对应的L值(例如 text、address、30 等)。请注意,有些值带引号,有些不带。有些使用空白字符,有些不使用。这些都是我们必须在正则表达式中考虑的因素。我们还必须考虑每个参数的不同顺序。例如,这
<input type="text" name="address" size=30 maxlength = "100">
与此相同
<input name="address" type="text" maxlength="100" size="30">

在示例应用程序中,我从 HTML 输入文件构建了一个单一字符串(我们将整个文件读入 CString 变量)。虽然这可能会在非常大的文件上引起问题,但出于我们的目的,我们假设文件相当小。我们需要整个字符串才能跨行匹配——但稍后会详细介绍。

ParseFile 方法

ParseFile 方法中,我们

  1. 传入要解析的 HTML 文件的文件名(必须包含
    和输入元素(例如 INPUT、SELECT、TEXTAREA),否则您将看不到任何输出。)
  2. 将整个文件读入字符串
  3. 创建一个正则表达式对象 (RegEx)
  4. 对文件字符串调用 Grep 以查找我们想要的模式,并将找到的匹配项放入 STL 向量中
  5. 遍历放入向量中的每个项目
  6. 调用 GetActualType(),它创建另一个正则表达式以获取我们找到的类型(例如 INPUT、TEXTAREA、SELECT)
  7. 调用 GetValue() 并传入键(例如 type、name 等)
  8. 生成并打印出包含我们获取的值的字符串

注意: 本文中的代码片段包含使用转义字符的正则表达式。因为这些是 C/C++ 字符串,所以这些转义字符必须转义两次。也就是说,正则表达式空白转义字符 (\s) 实际上会是这样:\\s。而引号会是这样:\\\" -- 第一个转义反斜杠,第二个转义引号。

BOOL CRegexTestDlg::ParseFile(CString filename)
{
    if (filename.IsEmpty())
    {
        return FALSE;
    }
    CString finalstring;


    this->m_mainEdit.SetWindowText("");

    CStdioFile htmlfile;
    CString inString = "";
    CString wholeFileString = "";
    std::string wholeFileStr = "";

    // Read entire file into a string.
    try{
        if (htmlfile.Open(filename, CFile::modeRead | 
                            CFile::typeText, NULL))
        {
            while (htmlfile.ReadString(inString))
            {
                wholeFileString += inString;
            }
            htmlfile.Close();
        }
    }
    catch (CFileException e)
    {
        MessageBox("The file " + filename + 
                    " could not be opened for reading", 
                    "File Open Failed",
                    MB_ICONHAND|MB_ICONSTOP|MB_ICONERROR );
        return FALSE;
    }

    // Need to convert string to a STL string for use in RegEx
    wholeFileStr = wholeFileString.GetBuffer(10);

    // Create our regular expression object
    // TRUE means that we want a match to be case-insensitive
    boost::RegEx expr("(<\s*(textarea|input|select)\\s*[^>]+>[^<>]*(</(select|textarea)>)?)", 
                      TRUE);

    // Create a vector to hold all matches
    std::vector<std::string> v;

    // Pass the vector and the STL string that hold the file contents
    // to the RegEx.Grep method.
    expr.Grep(v, wholeFileStr);

    // Create char array to hold actual type (e.g. input, select, textarea).
    char actualType[100];

    // vector v is now full of all matches. We iterate through them.
    for(int i = 0; i < v.size(); i++)
    {
        // Get the object at the current index and typecast to string
        std::string line = (std::string)v[i];

        // Get a pointer to the beginning of the character arrray
        const char *buf = line.c_str();

        // Create some temporary storage variables
        char name[100];
        char typeName[100];

        // Build output string.
        finalstring += "input, textarea, select?: ";
        GetActualType(buf, actualType);
        finalstring += actualType;
        finalstring += " -- ";
        GetValue("name", buf, name);
        finalstring += "name: ";
        finalstring += name;
        finalstring += " -- ";
        finalstring += "input type is: ";

        // If it's an input, get the type of input
        // (e.g. text, password, checkbox, radio, etc.)
        if(_stricmp("input", actualType) == 0)
        {
            GetValue("type", buf, typeName);
            finalstring += typeName;
        }
        // Otherwise, it doesn't apply.
        else
        {
            finalstring += "N/A";
        }
        finalstring += "\r\n";
        
    }


    // Populate text field with items
    this->m_mainEdit.SetWindowText(finalstring);

    return TRUE;

}

在此方法中,请特别注意这些行

// Create our regular expression object
boost::RegEx expr("(<\\s*(textarea|input|select)\\s*[^>]+>[^<>]*(</(select|textarea)>)?)", 
                  TRUE);

// Create a vector to hold all matches
std::vector<std::string> v;

// Pass the vector and the STL string that hold the file contents
// to the RegEx.Grep method.
expr.Grep(v, wholeFileStr);

expr 对象是用模式构造的。我将模式分解如下

(<\s*                          // Match on an open tag "<" and zero or
                               // more white space characters
          
(textarea|input|select)\s+[^>]+> // 1. Match on either textarea, input, or select
            1          2  3    
                                 // 2. look for one or more spaces next
                                 // 3. Match on one or more characters that
                                 //   are not a ">" until we find the end ">"
                                   
[^<>]*                         // Match on zero or more characters that are not
                               // "<" or ">"
                                   
(</(select|textarea)>)?) // Match on an end tag "</" and either a select or
                         // a text area. The question mark means that everything
                         // inside the quotes is optional(e.g. 0 or 1 occurrences).

注意: 在前面的描述中,转义字符没有转义两次。这是实际的正则表达式如果打印出来会是什么样子。

提醒一下,上面的正则表达式运算符表示

字符 描述 用法
* 匹配零个或多个前一个表达式。 "\s*" -- 零个或多个空白字符
+ 匹配一个或多个前一个表达式 "\s+" -- 一个或多个空白字符
[^] 否定集。 "[^<]" -- 匹配任何不是小于号 "<" 的字符。可以是要否定的字符列表(例如 [^<>/] -- 匹配任何不是小于号、大于号或正斜杠的字符)

Grep 方法接收对上面创建的向量的引用。在 Grep 调用之后,向量将包含所有找到的匹配项。使用 Grep() 而不是 Search()(这是另一个有用的方法),将允许您跨行边界进行匹配。这对于您读入的文件很重要——特别是允许相当宽松格式的 HTML 文件。例如,这

<input type="text" name="name">

与此相同

<input type="text"
        name="name">

在任何网络浏览器中。我们需要对此进行说明。如果您对大小写敏感性感到疑惑,请查看 RegEx 对象的实例化。第二个参数是一个布尔值。这表示您是否希望它不区分大小写——在示例代码中我们就是这样做的。

如果您想了解更多关于 boost Regex++ 库 API 的信息,请查看

GetActualType 方法

GetActualType 方法中,我们提取当前行正在处理的输入字段类型。请记住,在 ParseFile 方法中,我们确保至少有一种输入类型,因此此行几乎肯定会有一个。这是方法实现

BOOL CRegexTestDlg::GetActualType(const char *line, char *type)
{
    // Create a pattern to look for.
    char* pattern = "<\\s*((input|textarea|select))\\s*";
    // Create RegEx object with pattern. Should be case-insensitive
    RegEx exp(pattern, TRUE);

    // Search for the pattern. Use Search, not Grep since we have a single line.
    if(exp.Search(line))
    {
        // If found, copy the text of the first expression match to the
        // type variable.
        strcpy(type, exp[1].c_str());
        return TRUE;
    
    }
    // We didn't find anything. Just copy an empty string.
    strcpy(type, "");
    return FALSE;
}

看看模式本身

char* pattern = "<\\s*((input|textarea|select))\\s*";

这里我们说寻找一个开括号“<”和可能的空白字符。然后寻找“input”、“textarea”或“Select”。然后可能还有一些空白字符。注意 input|textarea|select 周围的两组括号。内层括号告诉我们这是一组可能的值。竖线 (|)(也称为“或”)告诉我们匹配可以包含这三个值中的任何一个。外层括号将我们找到的内容捕获到一个特殊变量中。因此,如果您通过我们的解析器运行此 HTML 代码

<input type= "text" name="email" size="20">

exp[1] 现在将包含单词“input”。如果您的行有其他用于捕获匹配部分的括号,它们将放在 exp[n] 中,其中 n 是从左到右、从外到内计算的当前括号集。

GetValue 方法

GetValue 方法中,我们传入一个要查找的键和一个指向要用值填充的变量的指针。

void CRegexTestDlg::GetValue(char *key, const char *str, char *val)
{
    char* tmpStr = "\\s*=\\s*\\\"?([^\"<>]+)\\\"?";

    char final[100];
    // We need to build the string so we know exactly what we're looking for.
    strcpy(final, key);
    strcat(final, tmpStr);

    // Create the RegEx object with the pattern.
    RegEx exp(final);
    // Search for the
    if(exp.Search(str))
    {
        // If found, copy what we found.
        strcpy(val, exp[1].c_str());
    }
    else
    {
        // Otherwise copy a string with the no<key> where <key> is the key passed in.
        sprintf(val, "no%s", key);
    }
}

看看这个表达式

char* tmpStr = "\\s*=\\s*\\\"?([^\"<>]+)\\\"?";

这是我们迄今为止最复杂的模式。首先,我们寻找一些可能的空白、一个等号和更多可能的空白。然后我们寻找一个开引号。问号表示前一个表达式出现0次或1次,因此如果 HTML 中不包含开引号,我们也会考虑到这一点。也就是说,如果行看起来像以下两种情况(注意引号),它仍然会找到匹配项

<input type="text" name="email">
<input type=text name=email>

接下来我们正在寻找除引号(")、开括号(<)或闭括号(>)以外的任何字符。这就是我们的值。请注意,这个值周围有括号,因为我们想将这个值捕获到我们的特殊变量 exp[n] 中。接下来我们正在寻找一个闭引号和一个可能的闭引号。

这就是我们对正则表达式的需求的终结。我们现在已经获得了我们正在寻找的值,并且可以在列表框中对其进行格式化和输出。如何处理这些值取决于您,但现在您拥有了准确有效地解析 HTML 所需的一切。示例代码可能需要一些调整,但总体而言它能完成任务。

运行示例

我提供的示例应用程序解析了一个包含表单的 HTML 文件。为了方便起见,我在项目中包含了一个 HTML 表单文件。文件名是 contact_form.html,它可以在项目的根目录中找到。运行应用程序时,只需单击“浏览...”按钮并选择此文件。然后单击“试试看!”

结论

虽然我们可以使用 strtok 或其他分词器来构建我们的解析器,但这些对于 HTML 来说并不完全理想,因为 HTML 可以非常自由(例如,这里有一个空格,那里有引号,但这里没有,换行等)。正则表达式非常适合这种文本解析。

Regex++ 是一个非常健壮的正则表达式库,您会在您的应用程序中发现它非常有用。查看示例项目并熟悉正则表达式语法。这将使您能够以最少的编码创建强大的文本解析器,并使您能够“掌握您的数据”。

© . All rights reserved.