将正则表达式匹配的强大功能带给 SQL

简介

在数据库中查找文本数据是应用程序中很常见的情形。有几种基于全文本的产品可用于 IBM® DB2® Universal Database™,包括 DB2 Text Information Extender 和 DB2 Net Search Extender。但是,DB2 Extenders® 不包括执行 正则表达式匹配的功能,该功能用来查找和替换字符串中的模式。本文描述了如何实现一个用户定义函数(UDF),该函数将普遍可用的正则表达式匹配库与 DB2 集成在一起。我们通过示例使用了 pcre 库(Perl 兼容的正则表达式),但也可以用任何其它库替换它。

本文中所提供的描述适用于 V7 和 V8 版本的 DB2 通用数据库的 Linux 版、UNIX® 版和 Windows® 版。

背景知识

本节描述了正则表达式是什么以及它们为什么有用。

正则表达式是什么?

正则表达式用于查找和替换字符串中的模式。正则表达式是用某种语法定义的,正则表达式引擎采用这种语法并将它与字符串进行比较。引擎返回字符串是否与语法匹配的指示;也即,该字符串是否包含能够从该语法派生的子串。此外,引擎还能够返回匹配的子串。术语“模式(pattern)”用来表示语法。

最基本的模式仅由单个字母组成。当与该模式进行比较时,包含这个字母的字符串就是一个“匹配”。例如,如果模式是“a”,则字符串“abcd”就是一个匹配,而字符串“xyz”则不是。正则表达式的强大功能来自于预定义的运算符(也称为元字符),它们可以用很小的空间来表示模式。根据“方言”和受支持的功能,可以使用不同的元字符。通常,其中的一些可用字符如下:

| — 二中择一
[ ] — 分组
* — 多次出现(也匹配零次出现)
+ — 多次出现(至少一次)
? — 随意的出现次数
\\\\ — 反斜杠

不同的系统实现了常用正则表达式的各种扩展。编程语言 Perl 中使用的正则表达式支持进一步的缩写。本文中所用的库实现了这些扩展。下面摘录了其中部分可以在 Perl 正则表达式语言中使用的缩写:

\\s — 任意空白字符
\\w — 任意字母数字字符
\\d — 任意数字字符

另一个更高级的示例是模式“[A-Z]* = ([0-9]|0x00);”。与这个模式相匹配的字符串包含这样的子串:它由几个大写字母、后面跟上一个空格、一个等号、另一个空格,然后是一个数字或字符串“0x00”组成。该子串的最后一个字符必须是分号。使用 Perl,这个模式可以表示为“\\w* = (\\d|0x00);”。“NM = 0x00;”和“X = 7;”是两个可以与该模式匹配的字符串。但字符串“Z = 123;”不能匹配,因为 123 是由三个数字所组成的。

DB2 中的字符串匹配

除了 Extender 以外,DB2 还允许几种用于文本比较的函数和运算符。但那些函数和运算符要么在用于模式匹配的功能方面有限制,要么就是会给可能使用它们的查询带来复杂性。这里简要地摘录几个可用的功能:

= 或 <> 谓词:逐字符地比较两个字符串是否相等。
LIKE 谓词:使用通配符的基本模式匹配。
LOCATE 函数:在字符串中查找子串。

尽管也可以用 SQL 运算符表示模式“[A-Z]* = ([0-9]|0x00);”,但那样会很麻烦。例如,下列 SELECT 语句的 WHERE 子句中所使用的谓词会匹配字符串“str”中等号之后的部分,如 清单 1所示:

SELECT str
FROM   strTable
WHERE ( str LIKE '% = 0;%' OR str LIKE '% = 1;%' OR str LIKE '% = 2;%' 
	OR str LIKE '% = 3;%' OR str LIKE '% = 4;%' OR str LIKE '% = 5;%' 
	OR str LIKE '% = 7;%' OR str LIKE '% = 7;%' OR str LIKE '% = 8;%' 
	OR str LIKE '% = 9;%' OR str LIKE '% = 0x00;%' )

这增加了可以匹配“[A-Z]*”子模式的谓词的复杂度,这可以使用对整个字符串进行迭代并进行逐字符比较的函数来完成,但您会发现使用内置功能既冗长又复杂。

示例方案

让我们定义下列清单( 清单 2)并插入几行:

CREATE TABLE strTable ( c1 INTEGER,str VARCHAR(500) );
INSERT INTO strTable VALUES ( 1,'some text;' ),( 2,'variable = 1234;' ),( 3,'var2 = ''string variable'';' ),( 4,'xyz = ' ),( 5,'myVar = 0x00;' ),( 6,'# comment' ),( 7,'abc = def' );

这个 清单及其数据被用于下面的所有示例。

SELECT * FROM strTable;
C1          STR
----------- ------------------------------
          1 some text;
          2 variable = 1234;
          3 var2 = 'string variable';
          4 xyz = 
          5 myVar = 0x00;
          6 # comment
          7 abc = def
  7 record(s) selected.

实现模式匹配函数

您可以使用 DB2 的可扩展机制,在 SQL 语句内使用 UDF,以便显著地改善这种情形。通过定义名为 regex1 的 UDF(它采用模式和字符串作为输入参数), 清单 1中的 WHERE 子句现在可以写得象 清单 3中所示的那样:

SELECT str
FROM   strTable
WHERE regex1('\\w* = (\\d|0x00);',str) = 1

在本示例中,使用带有 Perl 扩展的正则表达式来匹配完整的模式,而不仅仅是 清单 1中给出的 LIKE 谓词所对应的部分模式。正如您所看到的,使用函数来为该模式编写谓词比用 LIKE 谓词表示同样的语义要容易得多。

实现 UDF

在我的示例实现中,我选择了现有的名为 PCRE(Perl 兼容的正则表达式,Perl-compatible regular expression)的模式匹配引擎。该引擎提供了用来处理模式和执行匹配的 C API。该引擎和查询中所用的 SQL 语言之间“缺失的部分”是 UDF。该 UDF 由两部分组成:

  • 在数据库中创建(或注册)该函数的 CREATE FUNCTION 语句。
  • 该函数的主体,它实现了用于正则表达式匹配引擎的 C API 调用的封装器

清单 4显示了用于创建该函数的 SQL 语句。

CREATE FUNCTION regex1(pattern VARCHAR(2048),string CLOB(10M))
    RETURNS INTEGER
    SPECIFIC regexSimple
    EXTERNAL NAME 'regexUdf!regexpSimple'
    LANGUAGE C
    PARAMETER STYLE DB2SQL
    DETERMINISTIC
    NOT FENCED
    RETURNS NULL ON NULL INPUT
    NO SQL
    NO EXTERNAL ACTION
    ALLOW PARALLEL;

注:请参阅 DB2 SQL Reference以获取所有子句的详细含义。可以修改参数的长度以适应您的需求。我在此处展示某些值并没有任何推荐使用它们的用意。

第二部分由一小段 C 代码组成,它实现了 UDF 入口点。在查询执行期间,DB2 为每个要与模式匹配的行调用这个入口点。 清单 5 中的示例列出了该代码的清单。有关 pcre_* 函数和宏的描述,请参考 PCRE 库的文档。有关 C 代码的编译和共享库的构建,请参考 DB2 Application Development Guide

#include <pcre.h>
#include <sqludf.h>
void regexpSimple(
    // input parameters
    SQLUDF_VARCHAR *pattern,SQLUDF_CLOB *str,// output
    SQLUDF_INTEGER *match,// null indicators
    SQLUDF_NULLIND *pattern_ind,SQLUDF_NULLIND *str_ind,SQLUDF_NULLIND *match_ind,SQLUDF_TRAIL_ARGS)
{
    pcre *re = NULL;
    const char *error = NULL;
    int errOffset = 0;
    int rc = 0;
    // we assume successful return
    *match_ind = 0;
    // compile the pattern to its internal representation
    re = pcre_compile(pattern,0 /* default options */,&error,&errOffset,NULL);
    if (re == NULL) {
        snprintf(SQLUDF_MSGTX,70,"Regexp compilation failed at "
            "offset %d: %s\\n",errOffset,error);
        strcpy(SQLUDF_STATE,"38900");
        (*pcre_free)(re);
        return;
    }
    // match the string againts the pattern
    rc = pcre_exec(re,NULL,str->data,str->length,0);
    switch (rc) {
      case PCRE_ERROR_NOMATCH:
        *match = 0;
        break;
      case PCRE_ERROR_BADOPTION:
        snprintf(SQLUDF_MSGTX,"An unrecognized bit was set in the "
            "options argument");
        strcpy(SQLUDF_STATE,"38901");
        break;
      case PCRE_ERROR_NOMEMORY:
        snprintf(SQLUDF_MSGTX,"Not enough memory available.");
        strcpy(SQLUDF_STATE,"38902");
        break;
      default:
        if (rc < 0) {
            snprintf(SQLUDF_MSGTX,"A regexp match error "
                "occured: %d",rc);
            strcpy(SQLUDF_STATE,"38903");
        }
        else {
            *match = 1;
        }
        break;
    }
    // cleanup
    (*pcre_free)(re);
    return;
}

用法示例

下列查询试图从表 strTable 中找出包含注释文本的所有字符串。注释以“#”开头,所以模式是“#”后跟非空文本。

SELECT c1,str
FROM   strTable
WHERE  regex1('#\\s*\\w+',str) = 1;

结果只包含 c1 = 6 的行。

C1          STR
----------- -------------------------
          6 # comment;
  1 record(s) selected.

在第二个示例中,我们试图找到这种赋值形式的字符串;即“text = text”。为了进一步缩小范围,我们只查找那些右端为数值的赋值。将十六进制表示法作为有效数值对待。

SELECT c1,str
FROM   strTable
WHERE  regex1('\\w+\\s*=\\s*(\\d+|0x\\d\\d)',str) = 1;

除了 c1 为 2 或 5 的两行以外,其它行都不包含数值的赋值,因此不会出现在结果中:

C1          STR
----------- -------------------------
          2 variable = 1234;
          5 myVar = 0x00;
  2 record(s) selected.

改进性能

尽管上面的函数按照预期的方式工作,但还可以改进它以获得更佳的性能。注:函数内部的执行完成得越快,DB2 处理整个 SQL 语句的速度也就越快。

SQL 旨在处理多组行,这意味着通常会针对一个模式匹配多个行。在大多数情况下,模式本身对于整个 SQL 语句都是不变的;即,它不会随行的更改而更改。 清单 5 中的 C 代码展示了对每一行都调用函数 pcre_compile() ,该函数将给定模式转换成内部表示法。

DB2 通过使用所谓的“高速暂存(scratchpad)”提供了在 UDF 调用之间传递信息的机制。此外,您可以标识特定调用“类型”;即它是对该 UDF 的第一次调用、普通调用还是最后一次(最终)调用。使用高速暂存和调用类型,有可能只对模式编译一次,然后将该已编译模式的内部表示法重用于对该 UDF 的所有后续调用。在最后一次调用时,释放在处理期间分配的资源。

清单 6所示,对 CREATE FUNCTION 语句进行修改,告诉 DB2 向外部 C 代码提供高速暂存和调用类型:

CREATE FUNCTION regex2(pattern VARCHAR(2048),string CLOB(10M))
    RETURNS INTEGER
    SPECIFIC regexPerf
    EXTERNAL NAME 'regexUdf!regexpPerf'
    LANGUAGE C
    PARAMETER STYLE DB2SQL
    DETERMINISTIC
    NOT FENCED
    RETURNS NULL ON NULL INPUT
    NO SQL
    NO EXTERNAL ACTION
          
    SCRATCHPAD 50
    FINAL CALL
          
    ALLOW PARALLEL;

UDF 入口点看起来很不一样,因为必须改写函数内部的逻辑。参数方面唯一的更改是使用 SQLUDF_TRAIL_ARGS_ALL 代替了 SQLUDF_TRAIL_ARGS ,如 清单 7所示。

#include <pcre.h>
#include <sqludf.h>
// data structure mapped on the scratchpad for easier use and access
// to the objects
// the size of the scratchpad defined in the CREATE FUNCTION statement
// must be at least as large as sizeof(scratchPadMapping)
struct scratchPadMapping {
    pcre *re;
    pcre_extra *extra;
    const char *error;
    int errOffset;
};
void regexpPerf(
    // input parameters
    SQLUDF_VARCHAR *pattern,SQLUDF_TRAIL_ARGS_ALL) // SQLUDF_SCRAT & SQLUDF_CALLT
{
    int rc = 0;
    struct scratchPadMapping *scratch = NULL;
    // map the buffer of the scratchpad and assume successful return
    scratch = (struct scratchPadMapping *)SQLUDF_SCRAT->data;
    *match_ind = 0;
    switch (SQLUDF_CALLT) {
      case SQLUDF_FIRST_CALL:
        // initialize data on the scratchpad
        scratch->re = NULL;
        scratch->extra = NULL;
        scratch->error = NULL;
        scratch->errOffset = 0;
        // compile the pattern (only in the FIRST call
        scratch->re = pcre_compile(pattern,&scratch->error,&scratch->errOffset,NULL);
        if (scratch->re == NULL) {
            snprintf(SQLUDF_MSGTX,"Regexp compilation failed at "
                "offset %d: %s\\n",scratch->errOffset,scratch->error);
            strcpy(SQLUDF_STATE,"38900");
            rc = -1;
            break;
        }
        // further analyze the pattern (might return NULL)
        scratch->extra = pcre_study(scratch->re,&scratch->error);
        /* fall through to NORMAL call because DB2 expects a result
           already in the FIRST call */
      case SQLUDF_NORMAL_CALL:
        // match the current string
        rc = pcre_exec(scratch->re,scratch->extra,0);
        switch (rc) {
          case PCRE_ERROR_NOMATCH:
            *match = 0;
            rc = 0;
            break;
          case PCRE_ERROR_BADOPTION:
            snprintf(SQLUDF_MSGTX,"An unrecognized bit was set "
                "in the options argument");
            strcpy(SQLUDF_STATE,"38901");
            rc = -1;
            break;
          case PCRE_ERROR_NOMEMORY:
            snprintf(SQLUDF_MSGTX,"Not enough memory available.");
            strcpy(SQLUDF_STATE,"38902");
            rc = -1;
            break;
          default:
            if (rc < 0) {
                snprintf(SQLUDF_MSGTX,"A regexp match error "
                    "occured: %d",rc);
                strcpy(SQLUDF_STATE,"38903");
                rc = -1;
            }
            else {
                *match = 1;
                rc = 0;
            }
            break;
        }
        break;
      }
      // cleanup in FINAL call,or if we encountered an error in
      // the FIRST call (DB2 will make a FINAL call if we encounter
      // an error in any NORMAL call)
      if (SQLUDF_CALLT == SQLUDF_FINAL_CALL ||
          (SQLUDF_CALLT == SQLUDF_FIRST_CALL && rc < 0)) {
          (*pcre_free)(scratch->re);
          (*pcre_free)(scratch->extra);
      }
      return;
}

为了进一步改进该函数的性能,我添加了对函数 pcre_study() 的调用,该函数是由模式匹配引擎提供的。该函数进一步分析了该模式,并将额外的信息存储在独立的结构中。然后,在实际的匹配期间使用这些额外的信息来加快处理速度。通过使用一个非常简单的模式和大约 4000 行的表,我获得了 5% 的执行时间的改善。当然,模式越复杂,差异将越显著。

我先前提到该实现假定模式在处理期间不会随行的不同而更改。当然,如果模式确实更改了,您可以进行少量的改写以再次编译一个模式。要这样做,有必要跟踪当前(已编译的)模式并在每次调用中将它与所提供的模式进行比较。也可以在高速暂存中维护当前模式。但必须将它复制到独立的缓冲区,并且不能通过指针模式直接引用它,因为这个指针或它所引用的数据可能会更改或变为无效。至于相应的代码更改,就当作练习留给读者了。

返回匹配子串

大多数模式匹配引擎提供了一种方法,返回与指定模式或其一部分相匹配的子串。如果想在 SQL 中使用这种能力,则必须使用不同的方法来实现匹配函数。给定的字符串可能包含不止一个匹配的子串。例如,当解析类似“abc = 123;”或“def = 'some text';”这样的字符串时,用户可能会希望检索由等号分隔的两个子串。您可以使用模式“\\w+\\s*=\\s*(\\d+|'[\\w\\s]*');”来表示适用于该字符串的语法规则。Perl 兼容的正则表达式允许您捕获等号两边的子串。最后,必须将要捕获的子串用括号括起来。我已经用该方式编写了第二个子串,但第一个子串不是这样编写的。用于该用途的最终模式是这样的:

(\\w+)\\s*=\\s*(\\d+|'[\\w\\s]*');

当把这个模式应用于字符串“abc= 123;”或“def = 'some text';”时,“abc”或“def”分别与“(\\w+)”匹配,空格和等号是通过“\\s*=\\s*”查找的,并用另外的“(\\d+|'[\\w\\s*]')”涵盖了余下的子串。在“(\\d+|'[\\w\\s*]')”中,第一个选项与任何至少由一个数字“\\d+”组成的数匹配,而第二个选项解析任何由字母和空格组成的由单引号括起的字符串“'[\\w\\s]*'”。

在 DB2 中做到这一点的需求可以描述成:为一次 UDF 调用返回多个结果。换句话说,就是返回针对模式进行匹配的单个字符串的多个子串。DB2 的表函数是完成这一任务的完美工具。

实现表 UDF

和以前一样,必须在数据库中创建该函数。 清单 8中的下列语句正是用于这一任务的:

CREATE FUNCTION regex3(pattern VARCHAR(2048),string CLOB(10M))
    RETURNS TABLE ( position INTEGER,substring VARCHAR(2048) )
    SPECIFIC regexSubstr
    EXTERNAL NAME 'regexUdf!regexpSubstr'
    LANGUAGE C
    PARAMETER STYLE DB2SQL
    DETERMINISTIC
    NOT FENCED
    RETURNS NULL ON NULL INPUT
    NO SQL
    NO EXTERNAL ACTION
    SCRATCHPAD 50
    NO FINAL CALL
    DISALLOW PARALLEL;

实现该函数的实际逻辑的 C 代码与 清单 7中的代码非常相似,但根据表函数所必须满足的特殊需求对它进行了改编,如 清单 9所示。

#include <pcre.h>
#include <sqludf.h>
#include <sqlstate.h>
#include <string.h>
struct scratchPadMapping {
    pcre *re;
    pcre_extra *extra;
    const char *error;
    int errOffset;
    int numSubstr;
    int *substr;
    int currentSubstr;
};
void regexpSubstr(
    // input parameters
    SQLUDF_VARCHAR *pattern,// output
    SQLUDF_INTEGER *pos,SQLUDF_VARCHAR *substr,SQLUDF_NULLIND *pos_ind,SQLUDF_NULLIND *substr_ind,SQLUDF_TRAIL_ARGS_ALL) // SQLUDF_SCRAT & SQLUDF_CALLT
{
    int rc = 0;
    size_t length = 0;
    struct scratchPadMapping *scratch = NULL;
    // map the buffer of the scratchpad and assume NULL return
    scratch = (struct scratchPadMapping *)SQLUDF_SCRAT->data;
    *pos_ind = 0;
    *substr_ind = 0;
    switch (SQLUDF_CALLT) {
      case SQLUDF_TF_OPEN:
        // initialize data on the scratchpad
        scratch->re = NULL;
        scratch->extra = NULL;
        scratch->error = NULL;
        scratch->errOffset = 0;
        scratch->numSubstr = 0;
        scratch->substr = NULL;
        scratch->currentSubstr = 1; // skip the complete match
        // compile the pattern (only in the FIRST call
        scratch->re = pcre_compile(pattern,&scratch->error);
        // determine the number of capturing subpatterns
        rc = pcre_fullinfo(scratch->re,PCRE_INFO_CAPTURECOUNT,&scratch->numSubstr);
        if (rc) {
            snprintf(SQLUDF_MSGTX,"Could not retrieve info "
                "on pattern. (rc = %d)","38901");
            rc = -1;
            break;
        }
        // allocate memory for the substring indices
        {
            int size = (scratch->numSubstr+1)*3;
            scratch->substr = (int *)malloc(size * sizeof(int));
            if (!scratch->substr) {
                snprintf(SQLUDF_MSGTX,"Could allocate memory for "
                    "substring indices.");
                strcpy(SQLUDF_STATE,"38902");
                rc = -1;
                break;
            }
            memset(scratch->substr,size * sizeof(int));
            // match the current string
            rc = pcre_exec(scratch->re,scratch->substr,size);
        }
        switch (rc) {
          case PCRE_ERROR_BADOPTION:
            snprintf(SQLUDF_MSGTX,"38903");
            rc = -1;
            break;
          case PCRE_ERROR_NOMEMORY:
            snprintf(SQLUDF_MSGTX,"38904");
            rc = -1;
            break;
          case PCRE_ERROR_NOMATCH:
            scratch->currentSubstr = scratch->numSubstr + 1;
            rc = 0;
            break;
          default:
            if (rc < 0) {
                snprintf(SQLUDF_MSGTX,"38905");
                rc = -1;
                break;
            }
        }
        break;
      case SQLUDF_TF_FETCH:
        // skip capturing substrings without a match
        while (scratch->currentSubstr <= scratch->numSubstr &&
            (scratch->substr[2*scratch->currentSubstr] < 0 ||
                scratch->substr[2*scratch->currentSubstr+1] < 0)) {
            scratch->currentSubstr++;
        }
        // no more data to be returned
        if (scratch->currentSubstr > scratch->numSubstr) {
            strcpy(SQLUDF_STATE,SQL_NODATA_EXCEPTION);
            rc = 0;
            break;
        }
        // get the current substring
        *pos = scratch->currentSubstr;
        length = scratch->substr[2*scratch->currentSubstr+1] -
            scratch->substr[2*scratch->currentSubstr];
        strncpy(substr,str->data + scratch->substr[2*scratch->currentSubstr],length);
        substr[length] = '\\0';
        scratch->currentSubstr++;
    }
    // cleanup in CLOSE call,or if we encountered an error in
    // the OPEN call (DB2 will make a CLOSE call if we encounter
    // an error in any FETCH call)
    if (SQLUDF_CALLT == SQLUDF_TF_CLOSE ||
        (SQLUDF_CALLT == SQLUDF_TF_OPEN && rc < 0)) {
        (*pcre_free)(scratch->re);
        (*pcre_free)(scratch->extra);
        free(scratch->substr);
    }
    return;
}

正如我们对基本匹配函数所做的那样,您也可使用 FINAL CALL 定义表函数来改进性能。必须修改 C 代码以处理 SQLUDF_TF_FIRST 和 SQLUDF_TF_FINAL 调用。

用法示例

表函数可以在类似于如下所示的 SELECT 语句中使用:

SELECT c1,str,num,substr
FROM   strTable,TABLE ( regex3('(\\w+)\\s*=\\s*(\\d+|''[\\w\\s]*'');',str) ) AS sub(num,substr)

结果只包括拥有匹配模式的字符串。对于每个字符串,都在单独的行中显示所捕获的第一个和第二个子串。

C1          2                              NUM         4
----------- ------------------------------ ----------- -----------------------
          2 variable = 1234;                         1 variable
          2 variable = 1234;                         2 1234
          3 var2 = 'string variable';                1 var2
          3 var2 = 'string variable';                2 'string variable'
  4 record(s) selected.

下一个查询在结果集中用单独的列而不是单独的行返回这两个子串对。这样,用 SQL 语句进一步处理这些字符串以及它们的子串要容易些。该查询使用了公共表表达式(由关键字 WITH 表示)来确保在整个查询中只对每个字符串进行一次求值,而不是在中间表 s1s2 所需的每次子选择中都进行一次求值。

WITH substrings(c,substr) AS
   ( SELECT c1,substr
     FROM   strTable,str) )
               AS sub(num,substr) )
SELECT t.c1,s1.substr AS variable,s2.substr AS value
FROM   strTable AS t JOIN substrings AS s1 ON
          ( t.c1 = s1.c ) JOIN
       substrings AS s2 ON
          ( t.c1 = s2.c )
WHERE  s1.num = 1 AND s2.num = 2

这里使用了与前面查询中相同的模式。因此,可以从上面的表中派生出结果,但这一次,根据请求,这些 variable-value 对每个都位于单独的行中。

C1          VARIABLE                       VALUE
----------- ------------------------------ --------------------
          2 variable                       1234
          3 var2                           'string variable'
  2 record(s) selected.

结束语

在本文中,我相当简略地介绍了正则表达式以及 DB2 中可用的字符串比较和匹配功能。我还描述了为什么正则表达式的强大功能是如此有用。UDF 可以用来以两种形式在 DB2 中提供正则表达式。在第一种形式中,通过将字符串与给定模式进行比较来完成基本匹配。第二种形式是实现表函数,它从正则表达式抽取已捕获的子串,并将这些子串返回给 DB2,以便用在 SQL 语句的进一步处理中。我还给出了一些关于如何改进性能的提示。

参考资料

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


jquery.validate使用攻略(表单校验) 目录 jquery.validate使用攻略1 第一章&#160;jquery.validate使用攻略1 第二章&#160;jQuery.validate.js API7 Custom selectors7 Utilities8 Validato
/\s+/g和/\s/g的区别 正则表达式/\s+/g和/\s/g,目的均是找出目标字符串中的所有空白字符,但两者到底有什么区别呢? 我们先来看下面一个例子: let name = &#39;ye wen jun&#39;;let ans = name.replace(/\s/g, &#39;&#3
自整理几个jquery.Validate验证正则: 1. 只能输入数字和字母 /^[0-9a-zA-Z]*$/g jQuery.validator.addMethod(&quot;letters&quot;, function (value, element) { return this.optio
this.optional(element)的用法 this.optional(element)是jquery.validator.js表单验证框架中的一个函数,用于表单控件的值不为空时才触发验证。 简单来说,就是当表单控件值为空的时候不会进行表单校验,此函数会返回true,表示校验通过,当表单控件
jQuery.validate 表单动态验证 实际上jQuery.validate提供了动态校验的方法。而动态拼JSON串的方式是不支持动态校验的。牺牲jQuery.validate的性能优化可以实现(jQuery.validate的性能优化见图1.2 jQuery.validate源码 )。 也可
自定义验证之这能输入数字(包括小数 负数 ) &lt;script type=&quot;text/javascript&quot;&gt; function onlyNumber(obj){ //得到第一个字符是否为负号 var t = obj.value.charAt(0); //先把非数字的都
// 引入了外部的验证规则 import { validateAccountNumber } from &quot;@/utils/validate&quot;; validator.js /*是否合法IP地址*/ export function validateIP(rule, value,cal
VUE开发--表单验证(六十三) 一、常用验证方式 vue 中表单字段验证的写法和方式有多种,常用的验证方式有3种: data 中验证 表单内容: &lt;!-- 表单 --&gt; &lt;el-form ref=&quot;rulesForm&quot; :rules=&quot;formRul
正则表达式 座机的: 例子: 座机有效写法: 0316-8418331 (010)-67433539 (010)67433539 010-67433539 (0316)-8418331 (0316)8418331 正则表达式写法 0\d{2,3}-\d{7,8}|\(?0\d{2,3}[)-]?\d
var reg = /^0\.[1-9]{0,2}$/;var linka = 0.1;console.log (reg.test (linka)); 0到1两位小数正则 ^(0\.(0[1-9]|[1-9]{1,2}|[1-9]0)$)|^1$ 不含0、0.0、0.00 // 验证是否是[1-10
input最大长度限制问题 &lt;input type=&quot;text&quot; maxlength=&quot;5&quot; /&gt; //可以 &lt;input type=&quot;number&quot; maxlength=&quot;5&quot; /&gt; //没有效
js输入验证是否为空、是否为null、是否都是空格 目录 1.截头去尾 trim 2.截头去尾 会去掉开始和结束的空格,类似于trim 3.会去掉所有的空格,包括开始,结束,中间 1.截头去尾 trim str=str.trim(); // 强烈推荐 最常用、最实用 or $.trim(str);
正则表达式语法大全 字符串.match(正则):返回符合的字符串,若不满足返回null 字符串.search(正则):返回搜索到的位置,若非一个字符,则返回第一个字母的下标,若不匹配则返回-1 字符串.replace(正则,新的字符串):找到符合正则的内容并替换 正则.test(字符串):在字符串中
正整数正则表达式正数的正则表达式(包括0,小数保留两位): ^((0{1}.\d{1,2})|([1-9]\d.{1}\d{1,2})|([1-9]+\d)|0)$正数的正则表达式(不包括0,小数保留两位): ^((0{1}.\d{1,2})|([1-9]\d.{1}\d{1,2})|([1-9]+
JS 正则验证 test() /*用途:检查输入手机号码是否正确输入:s:字符串返回:如果通过验证返回true,否则返回false /function checkMobile(s){var regu =/[1][3][0-9]{9}$/;var re = new RegExp(regu);if (r
请输入保留两位小数的销售价的正则: /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/ 1.只能输入英文 &lt;input type=&quot;text&quot; onkeyup=&quot;value
判断价格的正则表达式 价格的正则表达式 /(^[1-9]\d*(\.\d{1,2})?$)|(^0(\.\d{1,2})?$)/; 1 解析:价格符合两种格式 ^ [1-9]\d*(.\d{1,2})?$ : 1-9 开头,后跟是 0-9,可以跟小数点,但小数点后要带上 1-2 位小数,类似 2,2
文章浏览阅读106次。这篇文章主要介绍了最实用的正则表达式整理,比如校验邮箱的正则,号码相关,数字相关等等,本文给大家列举的比较多,需要的朋友可以参考下。_/^(?:[1-9]d*)$/ 手机号
文章浏览阅读1.2k次。4、匹配中的==、an==、== an9、i9 == "9i"和99p==请注意下面这部分的作用,它在匹配中间内容的时候排除了说明:当html字符串如下时,可以匹配到两处,表示匹配的字符串不包含and且不包含空白字符。说明:在上面的正则表达式中,_gvim正则表达式匹配不包含某个字符串
文章浏览阅读897次。【代码】正则表达式匹配a标签的href。_auto.js 正则匹配herf