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

Mis*_*hko 9 javascript regex

我想构造一个将检查"路径"和"foo"参数(非负整数)的正则表达式."foo"是可选的.这应该:

比赛

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 = ''
Run Code Online (Sandbox Code Playgroud)

不符合

path?foo=37signals          # foo is not integer
path?foo=-8                 # foo cannot be negative
something?foo=1             # path not found
Run Code Online (Sandbox Code Playgroud)

此外,我想得到的价值foo,而不是额外的匹配.

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

Sam*_*Sam 19

答案

拧紧 你的辛勤工作,我只想要答案! 好的,你去 ......

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...');
    }
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
Run Code Online (Sandbox Code Playgroud)


研究

您的赏金请求要求"可信和/或官方来源",因此我将在查询字符串上引用RFC.

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

这看起来非常含糊:查询字符串以第一个开头,?并以#(锚的开始)或URI的结尾(或我们的情况下的字符串/行)结束.他们接着提到,大多数数据集是key=value对,这就是好像你有什么期望是解析(所以让我们假设这种情况).

但是,由于查询组件通常用于携带"key = value"对形式的标识信息,而一个常用值是对另一个URI的引用,因此有时可以更好地避免对这些字符进行百分比编码.

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

  1. 你的例子开始的路径,所以路径将是从字符串的开始,直到?(查询字符串), #(锚),或字符串的结尾.
  2. 查询字符串是iffy部分,因为RFC并没有真正定义"规范".浏览器倾向于期望从表单提交生成查询字符串,并且是key=value&字符附加的对的列表.保持这种心态:
    • 一键不能null,前面会有一个?或者&,并且不能包含一个=,&或者#.
    • 值是可选的,将在其前面key=,并且不能包含&#.
  3. #角色之后的任何东西都是锚.

让我们开始!

让我们从映射出我们的基本URI结构开始.你有一个路径,这是一个字符开始绳子,直到一个?,#或者是字符串的结尾.您有一个可选的查询字符串,它从a开始,?直到#字符串的一个或结尾.你有一个可选的锚点,从a开始#直到字符串的结尾.

^
([^?#]+)
(?:
  \?
  ([^#]+)
)?
(?:
  #
  (.*)
)?
$
Run Code Online (Sandbox Code Playgroud)

在深入研究查询字符串之前,让我们做一些清理工作.通过替换第一个捕获组,您可以轻松地要求路径等于某个值.无论你用(path)替换它,都必须跟随一个可选的查询字符串,一个可选的锚点和字符串的结尾(不多也不少).由于您不需要解析锚点,因此可以通过#在字符串的a 或末尾(即查询参数的末尾)结束匹配来替换捕获组.

^path
(?:
  \?
  ([^#\+)
)?
(?=#|$)
Run Code Online (Sandbox Code Playgroud)

停止瞎闹

好的,我一直在做很多设置而不用担心你的具体例子.下一个示例将匹配特定路径(path),并在捕获foo参数值时可选地匹配查询字符串.这意味着您可以在此处停止并检查有效匹配.如果匹配有效,则第一个捕获组必须null是非负整数.但这不是你的问题,是吗?这变得更加复杂,所以我将解释内联的表达式:

^            (?# 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)
Run Code Online (Sandbox Code Playgroud)

一些关键的要点:

  • Javascript不支持lookbehinds,这意味着你不能在密钥之前?&之前查看foo,这意味着你实际上必须匹配其中一个字符,这意味着查询字符串的开头(寻找a ?)必须是一个先行所以你实际上并没有匹配?.这也意味着您的查询字符串将始终至少包含一个字符(?),因此您希望重复查询字符串[^#]1次以上.
  • 现在,查询字符串在非捕获组中一次重复一个字符.无论它看到密钥foo,在这种情况下它会捕获可选值并继续重复.
  • 由于此非捕获查询字符串组一直重复到URI的锚点或结尾,因此第二个foo值(path?foo=123&foo=bar)将覆盖初始捕获的值.意味着您不会100%能够依赖上述解决方案.

最终方案?

好吧..现在我已经捕获了foo值,是时候在非正整数的值上杀死匹配了.

^            (?# 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)
Run Code Online (Sandbox Code Playgroud)

让我们仔细看看表达式中的一些juju:

  • 找到后foo=\d*,我们用一个前瞻,以确保它后跟&,#或者字符串的结尾(查询字符串值的结尾).
  • 但是如果还有更多的话foo=\d*,那么正则表达式会被交流发电机踢回到之前的通用[^#]匹配.这不好,因为它会继续匹配!因此,在查找通用查询字符串()之前,必须确保不查看(必须由第一次更改处理).这是负面前瞻派上用场的地方.[?&]foo[^#]foo(?![?&]foo=)
  • 这将适用于多个foo键,因为它们都必须等于非负整数.这foo也是可选的(或相等null).

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

  • +1努力工作.当然不是一件容易的事:) (6认同)
  • 当我打算使用`\ d*`时,我也意识到我正在使用`\ d +`(因为参数是可选的).另外,@ SteveChambers在列表中添加了一些[好的测试](http://regex101.com/r/uD9oY7/9). (2认同)

Ste*_*ers 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 = ''
Run Code Online (Sandbox Code Playgroud)

其他不匹配的测试

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
Run Code Online (Sandbox Code Playgroud)

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

path(?=(\?|$|#))(\?(.+&)?foo=(\d*)(&|#|$)|((?![?&]foo=).)*$)
Run Code Online (Sandbox Code Playgroud)

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

path(?=(?:\?|$|#))(?:\?(?:.+&)?foo=(\d*)(?:&|#|$)|(?:(?![?&]foo=).)*$)
Run Code Online (Sandbox Code Playgroud)

正则表达式可视化