RegexGen.js 是开发给 JavaScript 使用的正则表达式产生器,可以使用浅显易懂的语法来表现复杂的正则表达式。 RegexGen.js 的开发是受到 JSVerbalExpressions 的启发。
RegexGen.js 基本上是为那些已经了解正则表达式引擎运作原理,但是不常使用正则表达式的人而开发的。可以这么说,如果你切确知道存在某个表达式可以达成你的任务,但是却经常需要查表才能写出正确的表达式,那么 RegexGen.js 也许就可以帮到你。即使是正则表达式的初学者,也能够从 RegexGen.js 相对容易理解的表现方式,而快速地上手并使用简单的正则表达式。
简单地说,RegexGen.js 帮助人们:
- 以容易分解以及容易理解的方式表现正则表达式。
- 不必记忆正则表达式的『元字元 (meta-characters)』、『简写符号 (shortcuts)』,哪些字元在哪些情况下必须『跳脱 (escape)』,哪些情况下不需要?以及一些特殊的『极端情况 (corner cases)』,如 What literal characters should be escaped in a regex?。
- 重复使用正则表达式 (参考后面的『匹配 IP 位址』范例)。
RegexGen.js 努力减轻以下两个问题:
- 在撰写正则表达式的时候,难以记忆正确的语法,以及需要『跳脱』的字元等。
- 在正则表达式撰写完成后,难以阅读甚至无法理解其行为、运作原理。
RegexGen.js 的设计,谨守着下列目标:
- 写出来的程式码,应该易读易懂。
- 产出来的程式码,应该要像人工写的一样紧凑,不要为了产生器本身容易撰写,而产出机械式程式码。尤其是不要产出不必要的
{}
或()
。 - 不再需要手动对元字元进行转义。 (除了
\
元字元本身。或者使用了表达式置换 (regex overwrite) 功能。) - 如果产生器力有未逮,无法产生理想的子表达式,必须要能够在语法中直接指定替代的子表达式。也就是表达式置换功能。
产生器是以 regexGen()
函数的形式提供。
要输出正则表达式时,需要呼叫 regexGen()
函数,并将子表达式以参数的形式传入。
个别的子表达式之间,像一般参数一样,以 ,
逗号隔开。最终输出的正则表达式,将是这些子表达式组合而成的结果。
子表达式可以是字串、数字、一个标准的RegExp 物件,或透过regexGen()
函数提供的子方法(也就是子产生器),随意加以组合(参考后面的『[非正规BNF 语法] (#non-formal-bnf)』。
将字串传入regexGen()
函数,或text()
, maybe()
, anyCharOf()
及anyCharBut()
等子方法时,将自动视需要进行『跳脱』处理,因此你不需要记忆哪些字元需要在何时进行跳脱处里。
最后,regexGen()
函数回传一个 RegExp
物件,你接着可以像平常一样使用该正则表达式物件。
使用时,大致上像这样:
var regexGen = require('regexgen.js');
var regex = regexGen( sub-expression [, sub-expression ...] [, modifier ...] )
基本的使用方始,可以参考这里列出的『非正规 BNF 语法』:
(注意,因为这里列出的并不是严谨的BNF 语法,故这里称为『非正规 BNF 语法』。)
regex ::= regexGen( sub-expression [, sub-expression ...] [, modifier ...] )
sub-expression ::= string | number | RegExp object | term
term ::= sub-generator() [.term-quantifier()] [.term-lookahead()]
sub-generator() ::= regexGen.startOfLine() | regexGen.endOfLine()
| regexGen.wordBoundary() | regexGen.nonWordBoundary()
| regexGen.text() | regexGen.maybe() | regexGen.anyChar() | regexGen.anyCharOf() | regexGen.anyCharBut()
| regexGen.either() | regexGen.group() | regexGen.capture() | regexGen.sameAs()
| regex() | ... (checkout wiki for all sub-generators.)
term-quantifier() ::= .term-quantifier-generator() [.term-quantifier-modifier()]
term-quantifier-generator() ::= term.any() | term.many() | term.maybe() | term.repeat() | term.multiple()
term-quantifier-modifier() ::= term.greedy() | term.lazy() | term.reluctant()
term-lookahead() ::= term.contains() | term.notContains() | term.followedBy() | term.notFollowedBy()
modifier ::= regexGen.ignoreCase() | regexGen.searchAll() | regexGen.searchMultiLine()
详细的语法请参考 wiki 以及以下的说明及范例。更多的范例可以直接参考测试程式:test.js。
npm install regexgen.js
由于产生器是以 regexGen()
函数的形式提供,所有的子方法都是透过它来使用。建议可以先将它指定给比较简短的变数,譬如 _
符号:
var _ = require('regexgen.js');
var regex = _(
_.startOfLine(),
_.capture( 'http', _.maybe( 's' ) ), '://',
_.capture( _.anyCharBut( ':/' ).repeat() ),
_.group( ':', _.capture( _.digital().multiple(2,4) ) ).maybe(), '/',
_.capture( _.anything() ),
_.endOfLine()
);
var matches = regex.exec( url );
注意:虽然不推荐,但是如果你认为以上的作法仍然不够方便,同时也不介意污染全域物件 (譬如撰写小型工具程式时),你可以使用 regexGen.mixin()
函数,将 regexGen()
函数的所有的子方法,全部都汇出到全域物件中,这样就可以省略上面范例中 '_.' 的部份:
var regexGen = require('regexgen.js');
regexGen.mixin( global );
var regex = regexGen(
startOfLine(),
capture( 'http', maybe( 's' ) ), '://',
capture( anyCharBut( ':/' ).repeat() ),
group( ':', capture( digital().multiple(2,4) ) ).maybe(), '/',
capture( anything() ),
endOfLine()
);
var matches = regex.exec( url );
由 regexGen()
函数回传的是一个标准的 RegExp
物件,你可以像平常一样使用它。
不过为了提供除错功能,以及在处理字串时,方便提取内容,该 RegExp
物件另外附加了四个属性:
warnings
阵列, captures
阵列, extract()
方法以及 replace()
方法。
更多细节请参考 wiki 的说明。
检查一个字串,该字串必须至少包含六个字元,最多十个字元,字串必须混和数字、小写、大写英文字母,缺一不可。
这个范例取材自 Mastering Lookahead and Lookbehind 这篇文章。
var _ = require('regexgen.js');
var regex = _(
// 锚点: 匹配字串的起始位置
_.startOfLine(),
// 匹配六到十个英数字字元
_.word().multiple(6,10).
// 顺序环视:任意字元,直到发现一个小写英文字母 (忽略优先)
.contains( _.anything().reluctant(), _.anyCharOf(['a','z']) ).
// 顺序环视:任意字元,直到发现一个大写英文字母 (忽略优先)
.contains( _.anything().reluctant(), _.anyCharOf(['A','Z']) ).
// 顺序环视:任意字元,直到发现一个数字字元 (忽略优先)
.contains( _.anything().reluctant(), _.digital() ),
// 锚点: 匹配字串的结束位置
_.endOfLine()
);
输出为:
/^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)\w{6,10}$/
这个范例取材自 Mastering Regular Expressions 这本书。
var _ = require('regexgen.js');
// 0 ~ 199 的情形
var d1 = _.group( _.anyCharOf( '0', '1' ).maybe(), _.digital(), _.digital().maybe() );
// 200 ~ 249 的情形
var d2 = _.group( '2', _.anyCharOf( ['0', '4'] ), _.digital() );
// 250 ~ 255 的情形
var d3 = _.group( '25', _.anyCharOf( ['0', '5'] ) );
// 综和上面三点,批配 0 ~ 255 的情形
var d255 = _.capture( _.either( d1, d2, d3 ) );
var regex = _(
_.startOfLine(),
d255, '.', d255, '.', d255, '.', d255,
_.endOfLine()
);
输出为:
/^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$/
这个范例取材自 Mastering Regular Expressions 这本书。
var _ = require('regexgen.js');
var regex = _(
'(',
_.anyCharBut( '()' ).any(),
_.group(
'(',
_.anyCharBut( '()' ).any(),
')',
_.anyCharBut( '()' ).any()
).any(),
')'
);
输出为:
/\([^()]*(?:\([^()]*\)[^()]*)*\)/
这个范例取材自 Mastering Regular Expressions 这本书。
var _ = require('regexgen.js');
function nestingParentheses( level ) {
if ( level < 0 ) {
return '';
}
if ( level === 0 ) {
return _.anyCharBut( '()' ).any();
}
return _.either(
_.anyCharBut( '()' ),
_.group(
'(',
nestingParentheses( level - 1 ),
')'
)
).any();
}
一层巢状深度:
var regex = _(
'(', nestingParentheses( 1 ), ')'
);
输出为:
/\((?:[^()]|\([^()]*\))*\)/
三层巢状深度:
var regex = _(
'(', nestingParentheses( 3 ), ')'
);
输出为:
/\((?:[^()]|\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\))*\)/
这个范例取材自 Mastering Regular Expressions 这本书。
var _ = require('regexgen.js');
var regex = _(
'<',
_.either(
_.group( '"', _.anyCharBut('"').any(), '"' ),
_.group( "'", _.anyCharBut("'").any(), "'" ),
_.group( _.anyCharBut( '"', "'", '>' ) )
).any(),
'>'
);
输出为:
/<(?:"[^"]*"|'[^']*'|[^"'>])*>/
这个范例取材自 Mastering Regular Expressions 这本书。
var _ = require('regexgen.js');
var regexLink = _(
'<a',
_.wordBoundary(),
_.capture(
_.anyCharBut( '>' ).many()
),
'>',
_.capture(
_.label( 'Link' ),
_.anything().lazy()
),
'</a>',
_.ignoreCase(),
_.searchAll()
);
var regexUrl = _(
_.wordBoundary(),
'href',
_.space().any(), '=', _.space().any(),
_.either(
_.group( '"', _.capture( _.anyCharBut( '"' ).any() ), '"' ),
_.group( "'", _.capture( _.anyCharBut( "'" ).any() ), "'" ),
_.capture( _.anyCharBut( "'", '"', '>', _.space() ).many() )
),
_.ignoreCase()
);
输出为:
/<a\b([^>]+)>(.*?)<\/a>/gi
/\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^'">\s]+))/i
在浏览器中要遍历所有的连结,可以这么做:
var capture, guts, link, url, html = document.documentElement.outerHTML;
while ( (capture = regexLink.exec( html )) ) {
guts = capture[ 1 ];
link = capture[ 2 ];
if ( (capture = regexUrl.exec( guts )) ) {
url = capture[ 1 ] || capture[ 2 ] || capture[ 3 ];
}
console.log( url + ' with link text: ' + link );
}
这个范例取材自 Mastering Regular Expressions 这本书。
var _ = require('regexgen.js');
var regex = _(
_.startOfLine(),
'http', _.maybe( 's' ), '://',
_.capture( _.anyCharBut( '/:' ).many() ),
_.group( ':', _.capture( _.digital().many() ) ).maybe(),
_.capture( '/', _.anything() ).maybe(),
_.endOfLine()
);
输出为:
/^https?:\/\/([^/:]+)(?::(\d+))?(\/.*)?$/
在浏览器中要印出匹配的 URL,可以这么做:
var capture = location.href.match( regex );
var host = capture[1];
var port = capture[2] || 80;
var path = capture[3] || '/';
console.log( 'host:' + host + ', port:' + port + ', path:' + path );
这个范例取材自 Mastering Regular Expressions 这本书。
var _ = require('regexgen.js');
var regex = _(
_.startOfLine(),
// 以 . 号分隔的部份 . . .
_.either(
_.group(
_.anyCharOf( ['a', 'z'], ['0', '9'] ),
'.'
),
_.group(
_.anyCharOf( ['a', 'z'], ['0', '9'] ),
_.anyCharOf( '-', ['a', 'z'], ['0', '9'] ).multiple( 0, 61 ),
_.anyCharOf( ['a', 'z'], ['0', '9'] ),
'.'
)
).any(),
// 紧接着允许的域名分类 . . .
_.either(
'com', 'edu', 'gov', 'int', 'mil', 'net', 'org', 'biz', 'info', 'name', 'museum', 'coop', 'aero',
_.group( _.anyCharOf( ['a', 'z'] ), _.anyCharOf( ['a', 'z'] ) )
),
_.endOfLine()
);
输出为:
/^(?:[a-z0-9]\.|[a-z0-9][-a-z0-9]{0,61}[a-z0-9]\.)*(?:com|edu|gov|int|mil|net|org|biz|info|name|museum|coop|aero|[a-z][a-z])$/
这个范例取材自 Mastering Regular Expressions 这本书。
var _ = require('regexgen.js');
var regex = _(
_.either( _.startOfLine(), ',' ),
_.either(
// 要嘛是双引号所包围的内容
_.group(
// 起始双引号
'"',
_.capture(
_.anyCharBut( '"' ).any(),
_.group(
'""',
_.anyCharBut( '"' ).any()
).any()
),
// 结尾双引号
'"'
),
// 不然就是除了双引号、逗号之外的文字
_.capture(
_.anyCharBut( '",' ).any()
)
)
);
输出为:
/(?:^|,)(?:"([^"]*(?:""[^"]*)*)"|([^",]*))/
$ npm test