这个功能很棒,但它的主要缺点是它不处理以.co.uk或.com.au结尾的域。如何修改它来处理这个?
function parseUrl($url) {
$r = "^(?:(?P<scheme>\w+)://)?";
$r .= "(?:(?P<login>\w+):(?P<pass>\w+)@)?";
$r .= "(?P<host>(?:(?P<subdomain>[-\w\.]+)\.)?" . "(?P<domain>[-\w]+\.(?P<extension>\w+)))";
$r .= "(?::(?P<port>\d+))?";
$r .= "(?P<path>[\w/-]*/(?P<file>[\w-]+(?:\.\w+)?)?)?";
$r .= "(?:\?(?P<arg>[\w=&]+))?";
$r .= "(?:#(?P<anchor>\w+))?";
$r = "!$r!";
preg_match ( $r, $url, $out );
return $out;
}
澄清我寻找除parse_url()以外的东西的原因是我想剥离(可能是多个)子域名。
print_r(parse_url('sub1.sub2.test.co.uk'));
结果:
Array(
[scheme] => http
[host] => sub1.sub2.test.co.uk
)
我想要提取的是“test.co.uk”(没有子域名),所以首先使用parse_url是一个毫无意义的额外步骤,其中输出与输入相同。
答案 0 :(得分:9)
内置parse_url有什么问题?
答案 1 :(得分:4)
这可能是也可能没有意义,但这里是一个正则表达式我写的主要是符合RFC3986(它实际上稍微严格一些,因为它不允许一些更不寻常的URI语法):
~^(?:(?:(?P<scheme>[a-z][0-9a-z.+-]*?)://)?(?P<authority>(?:(?P<userinfo>(?P<username>(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=])*)?:(?P<password>(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=])*)?|(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:)*?)@)?(?P<host>(?P<domain>(?:[a-z](?:[0-9a-z-]*(?:[0-9a-z]))?\.)+(?:[a-z](?:[0-9a-z-]*(?:[0-9a-z]))?))|(?P<ip>(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d).(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d).(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d).(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)))(?::(?P<port>\d+))?(?=/|$)))?(?P<path>/?(?:(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:|@)+/)*(?:(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:|@)+/?)?)(?:\?(?P<query>(?:(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:|@)|/|\?)*?))?(?:#(?P<fragment>(?:(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:|@)|/|\?)*))?$~i
命名的组件是:
scheme
authority
userinfo
username
password
domain
ip
path
query
fragment
以下是生成它的代码(以及某些选项定义的变体):
public static function validateUri($uri, &$components = false, $flags = 0)
{
if (func_num_args() > 3)
{
$flags = array_slice(func_get_args(), 2);
}
if (is_array($flags))
{
$flagsArray = $flags;
$flags = array();
foreach ($flagsArray as $flag)
{
if (is_int($flag))
{
$flags |= $flag;
}
}
}
// Set options.
$requireScheme = !($flags & self::URI_ALLOW_NO_SCHEME);
$requireAuthority = !($flags & self::URI_ALLOW_NO_AUTHORITY);
$isRelative = (bool)($flags & self::URI_IS_RELATIVE);
$requireMultiPartDomain = (bool)($flags & self::URI_REQUIRE_MULTI_PART_DOMAIN);
// And we're away…
// Some character types (taken from RFC 3986: http://tools.ietf.org/html/rfc3986).
$hex = '[\da-f]'; // Hexadecimal digit.
$pct = "(?:%$hex{2})"; // "Percent-encoded" value.
$gen = '[\[\]:/?#@]'; // Generic delimiters.
$sub = '[!$&\'()*+,;=]'; // Sub-delimiters.
$reserved = "(?:$gen|$sub)"; // Reserved characters.
$unreserved = '[\w.\~-]'; // Unreserved characters.
$pChar = "(?:$unreserved|$pct|$sub|:|@)"; // Path characters.
$qfChar = "(?:$pChar|/|\?)"; // Query/fragment characters.
// Other entities.
$octet = '(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)';
$label = '[a-z](?:[0-9a-z-]*(?:[0-9a-z]))?';
$scheme = '(?:(?P<scheme>[a-z][0-9a-z.+-]*?)://)';
// Authority components.
$userInfo = "(?:(?P<userinfo>(?P<username>(?:$unreserved|$pct|$sub)*)?:(?P<password>(?:$unreserved|$pct|$sub)*)?|(?:$unreserved|$pct|$sub|:)*?)@)?";
$ip = "(?P<ip>$octet.$octet.$octet.$octet)";
if ($requireMultiPartDomain)
{
$domain = "(?P<domain>(?:$label\.)+(?:$label))";
}
else
{
$domain = "(?P<domain>(?:$label\.)*(?:$label))";
}
$host = "(?P<host>$domain|$ip)";
$port = '(?::(?P<port>\d+))?';
// Primary hierarchical URI components.
$authority = "(?P<authority>$userInfo$host$port(?=/|$))";
$path = "(?P<path>/?(?:$pChar+/)*(?:$pChar+/?)?)";
// Final bits.
$query = "(?:\?(?P<query>$qfChar*?))?";
$fragment = "(?:#(?P<fragment>$qfChar*))?";
// Construct the final pattern.
$pattern = '~^';
// Only include scheme and authority if the path is not relative.
if (!$isRelative)
{
if ($requireScheme)
{
// If the scheme is required, then the authority must also be there.
$pattern .= $scheme . $authority;
}
else if ($requireAuthority)
{
$pattern .= "$scheme?$authority";
}
else
{
$pattern .= "(?:$scheme?$authority)?";
}
}
else
{
// Disallow that optional slash we put in $path.
$pattern .= '(?!/)';
}
// Now add standard elements and terminate the pattern.
$pattern .= $path . $query . $fragment . '$~i';
// Finally, validate that sucker!
$components = array();
$result = (bool)preg_match($pattern, $uri, $matches);
if ($result)
{
// Filter out all of the useless numerical matches.
foreach ($matches as $key => $value)
{
if (!is_int($key))
{
$components[$key] = $value;
}
}
return true;
}
else
{
return false;
}
}
答案 2 :(得分:1)
替换此位:
(?P<extension>\w+)
使用:
(?P<extension>\w+(?:\.\w+)?)
(?:
... )
部分是非捕获组,?
使其成为可选组件。
我可能会更进一步,改变这一点:
(?P<extension>[a-z]{2,10}(?:\.[a-z]{2,10})?)
由于扩展名不包含数字或下划线,并且通常只有2/3个字母(我认为。博物馆是最长的,在6 ...所以10可能是最安全的。)
如果你这样做,你可能想要添加一个不区分大小写的标志,(或者也放入A-Z)。
根据你的评论,你想让匹配'懒惰'的子域成为一部分(只有在必要时才匹配),从而允许扩展捕获这两个部分。
要做到这一点,只需在量化器的末尾添加?
,即可更改:
(?P<subdomain>[-\w\.]+)
到
(?P<subdomain>[-\w\.]+?)
并且(理论上 - 没有PHP方便测试)只会在必要时使子域更长,所以应该允许扩展组适当匹配。
<强>更新强>
好的,假设您已经提取了完整的主机名(使用其他Q /注释中建议的parse_url),请尝试使用此匹配子域,域和扩展部分:
^(?P<subdomains>(?:[\w-]+\.)*?)(?P<domain>[\w-]+(?P<extension>(?:\.[a-z]{2,10}){1,2}))$
这将在子域的末尾(以及在扩展的开头)留下.
n,但是您可以使用substr($string,0,-1)
或类似内容删除它。
扩展表格以便于阅读:
^
(?P<subdomains>
(?:[\w-]+\.)*?
)
(?P<domain>
[\w-]+
(?P<extension>
(?:\.[a-z]{2,10}){1,2}
)
)$
(如果需要,可以添加注释来解释其中的任何内容吗?)
答案 3 :(得分:0)
parse_url()无法提取子域名和域名扩展名。你必须在这里发明自己的解决方案。
我认为正确的实现必须包含所有域名扩展的库,并定期更新。
https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains
http://www.iana.org/domains/root/db
https://publicsuffix.org/list/public_suffix_list.dat
https://raw.githubusercontent.com/publicsuffix/list/master/public_suffix_list.dat