如何在Javascript中使用正则表达式提取可选的查询参数

时间:2014-09-15 11:24:39

标签: javascript regex

我想构造一个将检查“path”和“foo”参数(非负整数)的正则表达式。 “foo”是可选的。它应该:

MATCH

path?foo=67                 # path found, foo = 67
path?foo=67&bar=hello       # path found, foo = 67
path?bar=bye&foo=1&baz=12   # path found, foo = 1
path?bar=123                # path found, foo = ''
path                        # path found, foo = ''

不匹配

path?foo=37signals          # foo is not integer
path?foo=-8                 # foo cannot be negative
something?foo=1             # path not found

此外,我希望获得foo的价值,而不执行其他匹配。

实现这一目标的最简单的正则表达式是什么?

10 个答案:

答案 0 :(得分:18)

答案

螺丝 您的辛勤工作,我只想要答案! Okay, here you go ......



var regex = /^path(?:(?=\?)(?:[?&]foo=(\d*)(?=[&#]|$)|(?![?&]foo=)[^#])+)?(?=#|$)/,
    URIs = [
      'path',                 // valid!
      'pathbreak',            // invalid path
      'path?foo=123',         // valid!
      'path?foo=-123',        // negative
      'invalid?foo=1',        // invalid path
      'path?foo=123&bar=abc', // valid!
      'path?bar=abc&foo=123', // valid!
      'path?bar=foo',         // valid!
      'path?foo',             // valid!
      'path#anchor',          // valid!
      'path#foo=bar',         // valid!
      'path?foo=123#bar',     // valid!
      'path?foo=123abc',      // not an integer
    ];
      
for(var i = 0; i < URIs.length; i++) {
    var URI = URIs[i],
        match = regex.exec(URI);

    if(match) {
        var foo = match[1] ? match[1] : 'null';
        console.log(URI + ' matched, foo = ' + foo);
    } else {
        console.log(URI + ' is invalid...');
    }
}
&#13;
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
&#13;
&#13;
&#13;


研究

您的赏金请求要求提供可信和/或官方来源&#34;,因此我引用RFC on query strings

  

查询组件包含非分层数据,与路径组件(第3.3节)中的数据一起用于标识URI方案和命名权限(如果有)范围内的资源。查询组件由第一个问号(&#34;?&#34;)字符表示,并以数字符号(&#34;#&#34;)字符或URI末尾终止。

这似乎有点模糊:查询字符串以第一个?开头,并以#(锚的开始)或URI的结尾(或我们的字符串/行)结束案件)。他们继续提到大多数数据集都在key=value对中,这就像你期望解析的那样(所以我们假设案件)。

  

但是,由于查询组件通常用于携带&#34; key = value&#34;形式的识别信息。对和一个经常使用的值是对另一个URI的引用,有时可以更好地避免对这些字符进行百分比编码。

考虑到这一切,让我们假设一些关于你的URI:

  1. 您的示例以路径开头,因此路径将从字符串的开头直到?(查询字符串),#(锚点)或字符串的结尾。< / LI>
  2. 查询字符串是iffy部分,因为RFC并没有真正定义&#34;规范&#34;。浏览器倾向于期望从表单提交生成查询字符串,并且是由key=value个字符附加的&对的列表。保持这种心态:
    • 密钥不能为null,前面会有?&,并且不能包含=&或{{1} }。
    • 值是可选的,前面会有#,不能包含key=&
  3. #个字符之后的任何内容都是锚点。

  4. 让我们开始吧!

    让我们从mapping out our basic URI structure开始。你有一个路径,它是从字符串开始直到#?或字符串结尾的字符。您有一个可选的查询字符串,它从#开始,一直到?或字符串的结尾。你有一个可选的锚点,从#开始直到字符串的结尾。

    #

    在深入查询字符串之前,先让我们do some clean up。通过替换第一个捕获组,您可以轻松地要求路径等于某个值。无论你用(^ ([^?#]+) (?: \? ([^#]+) )? (?: # (.*) )? $ )替换它,都必须跟随一个可选的查询字符串,一个可选的锚点和字符串的结尾(不多也不少)。由于您不需要解析锚点,因此可以通过在path或字符串末尾(查询参数的末尾)结束匹配来替换捕获组。

    #

    停止混乱

    好的,我一直在做很多设置而不用担心你的具体例子。下一个示例将匹配特定路径(^path (?: \? ([^#\+) )? (?=#|$) ),并可选地匹配查询字符串,同时捕获path参数的值。这意味着您可以在此处停止并检查有效匹配。如果匹配有效,则第一个捕获组必须为foo或非负整数。但这不是你的问题,是吗? This got a lot more complicated,所以我要解释内联的表达式:

    null

    这里有一些关键的要点:

    • Javascript不支持lookbehinds,这意味着您无法在密钥^ (?# match beginning of the string) path (?# match path literally) (?: (?# begin optional non-capturing group) (?=\?) (?# lookahead for a literal ?) (?: (?# begin optional non-capturing group) [?&] (?# keys are preceded by ? or &) foo (?# match key literally) (?: (?# begin optional non-capturing group) = (?# values are preceded by =) ([^&#]*) (?# values are 0+ length and do not contain & or #) ) (?# end optional non-capturing group) | (?# OR) [^#] (?# query strings are non-# characters) )+ (?# end repeating non-capturing group) )? (?# end optional non-capturing group) (?=#|$) (?# lookahead for a literal # or end of the string) 之前查看?&,这意味着您实际上必须匹配一个这些字符的含义,意味着查询字符串的开头(查找foo)必须是预测,以便您实际上不匹配?。这也意味着您的查询字符串将始终至少包含一个字符(?),因此您希望重复查询字符串? 1次。
    • 查询字符串现在在非捕获组中一次重复一个字符。除非它看到键[^#],否则它会捕获可选值并继续重复。
    • 由于此非捕获查询字符串组一直重复,直到URI的锚点或结尾,第二个foo值(foo)将覆盖初始捕获的值。意味着你不会100%能够依赖上述解决方案。

    最终解决方案?

    好的..现在我已经抓住了path?foo=123&foo=bartime to kill the match on a values that are not positive integers

    foo

    让我们仔细研究一下进入该表达式的juju

    • 找到^ (?# match beginning of the string) path (?# match path literally) (?: (?# begin optional non-capturing group) (?=\?) (?# lookahead for a literal ?) (?: (?# begin optional non-capturing group) [?&] (?# keys are preceeded by ? or &) foo (?# match key literally) = (?# values are preceeded by =) (\d*) (?# value must be a non-negative integer) (?= (?# begin lookahead) [&#] (?# literally match & or #) | (?# OR) $ (?# match end of the string) ) (?# end lookahead) | (?# OR) (?! (?# begin negative lookahead) [?&] (?# literally match ? or &) foo= (?# literally match foo=) ) (?# end negative lookahead) [^#] (?# query strings are non-# characters) )+ (?# end repeating non-capturing group) )? (?# end optional non-capturing group) (?=#|$) (?# lookahead for a literal # or end of the string) 后,我们使用前瞻来确保它后跟foo=\d*&或字符串的结尾(查询字符串值的结尾)
    • 但是..如果#还有更多内容,那么正则表达式会被交流发电机踢回foo=\d*之前[^#]的{​​{1}}匹配。这不好,因为它会继续匹配!因此,在查找通用查询字符串([?&])之前,必须确保没有查看foo必须由第一次更改处理)。这就是负向前瞻[^#]派上用场的地方。
    • 这将适用于多个foo键,因为它们都必须等于非负整数。这样,(?![?&]foo=)也可以是可选的(或等于foo)。

    免责声明:大多数Regex101演示使用PHP进行更好的语法突出显示,并在负字符类中包含foo,因为有多行示例。

答案 1 :(得分:5)

好问题!起初看起来相当简单......但是有一些很多的陷阱。建议检查任何声明的解决方案将处理以下事项:

其他比赛测试

path?                  # path found, foo = ''
path#foo               # path found, foo = ''
path#bar               # path found, foo = ''
path?foo=              # path found, foo = ''
path?bar=1&foo=        # path found, foo = ''
path?foo=&bar=1        # path found, foo = ''
path?foo=1#bar         # path found, foo = 1
path?foo=1&foo=2       # path found, foo = 2
path?foofoo=1          # path found, foo = ''
path?bar=123&foofoo=1  # path found, foo = ''

其他不匹配的测试

pathbar?               # path not found
pathbar?foo=1          # path not found
pathbar?bar=123&foo=1  # path not found
path?foo=a&foofoo=1    # not an integer
path?foofoo=1&foo=a    # not an integer

我能提出的最简单的正则表达式适用于所有这些其他情况:

path(?=(\?|$|#))(\?(.+&)?foo=(\d*)(&|#|$)|((?![?&]foo=).)*$)

但是,会建议将?:添加到未使用的捕获组,以便忽略它们,并且您可以轻松地从第1组获取foo值 - 请参阅Debuggex Demo

path(?=(?:\?|$|#))(?:\?(?:.+&)?foo=(\d*)(?:&|#|$)|(?:(?![?&]foo=).)*$)

Regular expression visualization

答案 2 :(得分:4)

^path\b(?!.*[?&]foo=(?!\d+(?=&|#|$)))(?:.*[?&]foo=(\d+)(?=&|#|$))?

基本上我把它分成三部分

^path\b                         # starts with path
(?!.*[?&]foo=(?!\d+(?=&|#|$)))  # not followed by foo with an invalid value
(?:.*[?&]foo=(\d+)(?=&|#|$))?   # possibly followed by foo with a valid value

在此处查看验证http://regexr.com/39i7g

注意事项:

将匹配path#bar=1&foo=27

path?foo=

不匹配 OP没有提到这些要求,因为他想要一个简单的正则表达式(矛盾?),我并没有尝试解决它们。

答案 3 :(得分:2)

path.+?(?:foo=(\d+))(?![a-zA-Z\d])|path((?!foo).)*$

你可以尝试一下。参见演示。

http://regex101.com/r/jT3pG3/10

答案 4 :(得分:2)

您可以尝试以下正则表达式:

path(?:.*?foo=(\d+)\b|()(?!.*foo))

regex101 demo

path后有两种可能的匹配:

.*?foo=(\d+)\b,即foo后跟数字。

OR

如果前方没有()(?!.*foo),则

foo为空字符串。

如果您不希望正则表达式解释\b周围的其他单词(例如,另一个名为barfoobar的参数),请添加一些单词边界(foo)。

path(?:.*?\bfoo=(\d+)\b|()(?!.*\bfoo\b))

答案 5 :(得分:1)

您可以检查是否存在3个 rd 匹配的组。它不存在,foo值为null;否则,就是小组本身:

/^(path)(?:$|\?(?:(?=.*\b(foo=)(\d+)\b.*$)|(?!foo=).*?))/gm

关于regex101的示例:http://regex101.com/r/oP6lU7/1

答案 6 :(得分:1)

处理javascript引擎以使正则表达式除了与PCRE相比所有的缺点之外,不知何故是令人愉快的!

我做了这个RegEx,简单易懂:

^(?=path\?).*foo=(\d*)(?:&|$)|path$

<强> 说明

^(?=path\?)             # A positive lookahead to ensure we have "path" at the very begining
.*foo=(\d*)(?:&|$)  # Looking for a string includes foo=(zero or more digits) following a "&" character or end of string
|                       # OR
path$                   # Just "path" itself

Runnable片段:

&#13;
&#13;
var re = /^(?=path\?).*foo=(\d*)(?:&|$)|path$/gm; 
var str = 'path?foo=67\npath?foo=67&bar=hello\npath?bar=bye&foo=1&baz=12\npath\npathtest\npath?foo=37signals\npath?foo=-8\nsomething?foo=1';
var m, n = [];
 
while ((m = re.exec(str)) != null) {
    if (m.index === re.lastIndex) {
        re.lastIndex++;
    }
    n.push(m[0]);
}

alert( JSON.stringify(n) );
&#13;
&#13;
&#13;

Live demo 了解更多详情

答案 7 :(得分:1)

path(?:\?(?:[^&]*&)*foo=([0-9]+)(?:[&#]|$))?

这和大多数一样短,读得更直接,因为在字符串中出现一次的内容在RE中出现一次。

我们匹配:

  1. 初始路径
  2. 问号,(或跳到结束)
  3. 一些由&符号终止的块
  4. 我们的参数分配
  5. 结束确认,要么开始下一个句法元素,要么结束行
  6. 不幸的是,当foo参数被省略时,它将foo与None匹配,而不是'',而在Python(我选择的语言)中被认为更合适。如果你愿意,你可以抱怨,或者只是 '

答案 8 :(得分:0)

根据OP的数据,这里是我的尝试模式

^(path)\b(?:[^f]+|f(?!oo=))(?!\bfoo=(?!\d+\b))(?:\bfoo=(\d+)\b)?

如果找到路径:子模式#1将包含&#34;路径&#34;
如果foo有效:子模式#2将包含&#34; foo值,如果有的话#34;

Demo

  • ^(path)\b&#34;路径&#34;
  • (?:[^f]+|f(?!oo=))后面跟着&#34; foo =&#34;
  • (?!\bfoo=(?!\d+\b)) if&#34; foo =&#34;发现它除了\d+\b
  • 之外什么也看不见
  • (?:\bfoo=(\d+)\b)?如果有效&#34; foo =&#34;被发现,捕获&#34; foo&#34;值

答案 9 :(得分:-1)

t = 'path?foo=67&bar=hello';
console.log(t.match(/\b(foo|path)\=\d+\b/))

正则表达式/\b(foo|path)\=\d+\b/