Javascript中的URL解析问题

4
我正在尝试提取URL的主机部分(不包括协议和www前缀)以及主机后面的内容(如果有)。目前的正则表达式如下:
/^(?:http|https)?(?::\/\/)?(?:www\.)?(.*?)(\/.*)/;

这适用于包含所有内容的url,例如:

http://www.site.com/part1/part2?key=value#blub

但是如果我将路径捕获组标记为可选项:
/^(?:http|https)?(?::\/\/)?(?:www\.)?(.*?)(\/.*)?/

它不再匹配。为什么?

现在,如果我让第一个变量与之匹配:

http://site.com

它提取作为第一个值(权限),//site.com作为第二个值(路径)。

我没有期望这能够工作,因为它没有路径并且路径没有被标记为可选。但是我仍然对这个结果感到好奇,因为我只有这两个捕获组 - (.*?)(\/.*)

http://jsfiddle.net/U2tKT/1/

有人可以解释一下吗?请不要给我完整的url解析解决方案的链接,我知道有很多这样的链接,但我想了解我的正则表达式有什么问题(以及如何解决)。

谢谢。

3个回答

3

当我即将点击提交按钮时,user1436026发表了帖子,但以下是他的意见:

你的域名(权威)模式被标记为“ungreedy”,它尽可能少地匹配。在你的情况下,它实际上满足了不匹配任何内容的模式 - 这几乎是最小的匹配。相反,你想要的是尽可能多地匹配域名,直到你确定它所匹配的内容不再是域名(我更改了正则表达式来匹配除/之外的任何字符,并尽可能多地找到匹配项。)

/^(?:http|https)?(?::\/\/)?(?:www\.)?([^\/]+)(\/.*)?/

我知道您特别声明不想使用JS中的任何URL解析方案,但是您是否知道JS已经内置了这个功能? :)
var link = document.createElement('a');
link.href="http://www.site.com/part1/part2?key=value#blub";
auth=link.hostname; //www.site.com
path=link.pathname; // /part1/part2

啊...对于 / 的否定运算符,很有道理。我怀疑我的问题与贪婪的事情有关,但不知道确切是什么 :) 非常感谢。 - User
我现在看到了你的编辑 - 不,我不知道。也许我会用那个改变我的正则表达式。再次感谢(不能投更多票)! - User

2
在你的正则表达式/^(?:http|https)?(?::\/\/)?(?:www\.)?(.*?)(\/.*)?/的末尾,(.*?)(因为它具有?修饰符)试图尽可能匹配少的内容以满足正则表达式。由于你将正则表达式的最后一部分设为可选,所以(.*?)不需要匹配任何内容就可以满足正则表达式,因为允许(\/.*)?匹配空。而当你把正则表达式的最后一部分设为必需时,(\/.*)(.*?)被迫匹配足够的内容以使(\/.*)匹配成功。

谢谢您的解释(+1),但是另一个答案也包含了一个解决方案,所以我会选择那个 :) - User

1

RFC3986

互联网工程任务组(IETF)的请求评论(RFC)文档编号3986,标题为:“统一资源标识符(URI):通用语法”(RFC3986),是描述构成有效通用统一资源标识符(URI)所有组件精确语法的权威标准。 附录B 提供了您需要的正则表达式:

^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?

使用这个正则表达式,URI的各个部分被存储如下:
方案 = $2 权限 = $4 路径 = $5 查询 = $7 片段 = $9
为了记录上述正则表达式,我已经在自由空间模式下进行了重写,并添加了注释和缩进。下面以一个经过测试的PHP脚本的形式呈现,该脚本解析给定URI字符串的所有主要部分:
<?php // test.php Rev:20130830_0800

$re_rfc3986_parse_generic_uri = '%
    # Parse generic URI according to RFC3986 Appendix B.
    ^             # Anchor to start of string.
    (?:           # Group for optional scheme.
      ([^:/?#]+)  # $1: Uri SCHEME.
      :           # Scheme ends with ":".
    )?            # Scheme is optional.
    (?:           # Group for optional authority.
      //          # Authority starts with "//"
      ([^/?#]*)   # $2: Uri AUTHORITY.
    )?            # Authority is optional.
    ([^?#]*)      # $3: Uri PATH (required).
    (?:           # Group for optional query.
      \?          # Query starts with "?".
      ([^#]*)     # $4: Uri QUERY.
    )?            # Query is optional.
    (?:           # Group for optional fragment.
      \#          # Fragment starts with "#".
      (.*)        # $5: Uri FRAGMENT.
    )?            # Fragment is optional.
    $             # Anchor to end of string.
    %x';

$text = "http://www.site.com/part1/part2?key=value#blub";

if (preg_match($re_rfc3986_parse_generic_uri, $text, $matches)) {
    print_r($matches);
} else {
    echo("String is not a valid URI");
}
?>

原始正则表达式进行了两个功能性更改:1)不必要的捕获组被转换为非捕获组,2)在表达式末尾添加了字符串结束锚点$。请注意,可以使用命名捕获组而不是使用编号捕获组创建一个更易读的版本,但这种方法不能直接转换为JavaScript语法。
PHP脚本输出:
Array ( [0] => http://www.site.com/part1/part2?key=value#blub [1] => http [2] => www.site.com [3] => /part1/part2 [4] => key=value [5] => blub )
JavaScript解决方案:
以下是一个经过测试的JavaScript函数,将有效的URI分解为其各个组件:
// Parse a valid URI into its various parts per RFC3986.
function parseValidURI(text) {
    var uri_parts;
    var re_rfc3986_parse_generic_uri =
    /^(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/;
    // Use String.replace() with callback function to parse the URI.
    text.replace(re_rfc3986_parse_generic_uri,
        function(m0,m1,m2,m3,m4,m5) {
            uri_parts = {
                scheme      : m1,
                authority   : m2,
                path        : m3,
                query       : m4,
                fragment    : m5
            };
            return; // return value is not used.
        });
    return uri_parts;
}

请注意,如果URI字符串中不存在返回对象的非路径属性,则这些属性可能为undefined。此外,如果URI字符串不匹配此正则表达式(即明显无效),则返回值为undefined
注意事项:
- 通用URI的唯一组件是路径(本身可以为空)。 - 空字符串是有效的URI! - 上述正则表达式不验证URI,而是解析给定的有效URI。 - 如果上述正则表达式无法匹配URI字符串,则该字符串不是有效的URI。但反之不成立——如果字符串与上述正则表达式匹配,则并不意味着URI有效,而只是表示它可解析为URI。 - 对于那些有兴趣验证URI并进一步分解它的人,我写了一篇文章,其中将RFC3986附录A中定义的所有部分转换为正则表达式语法。请参见: 正则表达式URI验证 祝你使用正则表达式愉快!

喜欢正则表达式,非常深入的研究和非常好的文章。只是想指出 parse_url 是 PHP 解决方案的一个好选择。 :) - Sean Johnson
@Sean Johnson - 谢谢。上面提供PHP版本的唯一原因是为了提供RFC3986正则表达式的注释和自由间隔模式版本的载体。我同意,如果存在本地库函数,则最好使用该函数而不是尝试重新发明轮子。 - ridgerunner
不错 (+1),虽然不完全符合我的要求 - 它捕获了 "www",并且不强制协议为 http/https。但我想在匹配的部分上进行这些检查/过滤比将其放入正则表达式中更清晰。然而,这个正则表达式还有其他问题,至少对于我的用例来说,它会将字符串 "www.bla.com" 或 "bla.com" 匹配为路径,并将权限留空。为了解决这个问题,我将其更改为 ^(?:([^:\/?#]+):)?(?:\/\/)?([^\/?#]*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?$ - User

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接