如何在Perl中很好地格式化URL?

时间:2010-09-20 07:06:22

标签: perl url

我有一堆网址,我必须转为链接:

for my $url (@url_list) {
    say "<a href='$url'>$url</a>";
}

是否有用于使可见网址更好的模块?有点像这样:

http://www.foo.com/ → www.foo.com
http://www.foo.com/long_path → www.foo.com/lo…

我知道一个简单的正则表达式可能会在这里做,但我被CPAN宠坏了。 :)

5 个答案:

答案 0 :(得分:5)

RFC 2396的附录B指定了解析URI引用的正则表达式。调整一下以获得你想要的东西:

#! /usr/bin/perl

use warnings;
use strict;

use 5.10.0;  # for defined-or (//)

my $uri = qr{
  ^
  (?:([^:/?\#]+):)?  # scheme = $1
  (?://([^/?\#]*))?  # authority = $2
  ([^?\#]*)          # path = $3
  (\?[^\#]*)?        # query = $4
  (\#.*)?            # fragment = $5
}x;

上面的代码使用/x modifier

  

它告诉正则表达式解析器忽略大多数空格,这些空格既不是反斜杠也不是字符类。您可以使用它将正则表达式分解为(略微)更易读的部分。与普通的Perl代码一样,#字符也被视为引入注释的元字符。

但我们希望匹配文字#字符,如果它们存在,这意味着我需要用反斜杠来逃避它们。出于习惯,我从qr/开始,但由于模式中的斜杠,不得不更改分隔符。

一些测试用例:

my @cases = qw(
  ftp://www.foo.com.invalid/
  http://www.foo.com.invalid/
  http://www.foo.com.invalid/long_path
  http://www.foo.com.invalid/?query
  http://www.foo.com.invalid?query
  http://www.foo.com.invalid/#fragment
  http://www.foo.com.invalid#fragment
);

有点逻辑

for (@cases) {
  my $nice;
  if (my($scheme,$auth,$path,@rest) = /$uri/) {
    if ($scheme eq "http" && defined $auth) {
      if (grep defined, @rest) {
        $nice = join "" => map $_ // "" => $auth, $path, @rest;
      }
      else {
        $nice = $auth
              . ($path eq "/" ? "" : $path);
      }
    }
    else {
      $nice = $_;
    }
  }

  print "$_ → $nice\n";
}

和输出:

ftp://www.foo.com.invalid/ → ftp://www.foo.com.invalid/
http://www.foo.com.invalid/ → www.foo.com.invalid
http://www.foo.com.invalid/long_path → www.foo.com.invalid/long_path
http://www.foo.com.invalid/?query → www.foo.com.invalid/?query
http://www.foo.com.invalid?query → www.foo.com.invalid?query
http://www.foo.com.invalid/#fragment → www.foo.com.invalid/#fragment
http://www.foo.com.invalid#fragment → www.foo.com.invalid#fragment

答案 1 :(得分:4)

诀窍在于弄清楚你想如何打印各种URL,所以在这种情况下你需要告诉你的脚本在每种情况下做什么:

use URI;

while( <DATA> ) {
    chomp;
    my $uri = URI->new( $_ );

    my $s = $uri->scheme;
    my $rest = do {
        if( $s =~ /(?:https?|ftp)/ ) {
            $uri->host . $uri->path_query
            }
        elsif( $s eq 'mailto' ) {
            $uri->path
            }
        elsif( ! $s ) {
            $uri
            }
        };

    print "$uri -> $rest\n";
    }

__END__
http://www.example.com/foo/bar.html
www.example.com/foo/bar.html
ftp://www.example.com
mailto:joe@example.com
https://www.example.com/foo?a=b;c=d
http://joe:password@www.example.com/login

这会产生:

http://www.example.com/foo/bar.html -> www.example.com/foo/bar.html
www.example.com/foo/bar.html -> www.example.com/foo/bar.html
ftp://www.example.com -> www.example.com
mailto:joe@example.com -> joe@example.com
https://www.example.com/foo?a=b;c=d -> www.example.com/foo?a=b;c=d
http://joe:password@www.example.com/login -> www.example.com/login

如果您想要特定URL的不同内容,您只需要为它创建一个分支并将所需的部分组合在一起。请注意,URI也处理无方案URI。

如果您不希望长漂亮的URI字符串用于漂亮的打印,那么您可能会抛出这样的内容,以便在这么多字符后切断字符串:

substr( $rest, 20 ) = '...' if length $rest > 20;

这是一个given的解决方案,它稍微清洁一点,但也有点丑陋。这是Perl 5.010版本:

use 5.010;
use URI;

while( <DATA> ) {
    chomp;
    my $uri = URI->new( $_ );

    my $r;
    given( $uri->scheme ) {
        when( /(?:https?|ftp)/  ) { $r = $uri->host . $uri->path_query }
        when( 'mailto' )          { $r = $uri->path }       
        default                   { $r = $uri }
        }


    print "$uri -> $r\n";
    }

这是更丑陋的,因为我必须重复$r的任务。 Perl 5.14将解决这个问题,尽管让given有一个返回值。由于该稳定版本尚不可用,您必须使用实验性的5.13轨道:

use 5.013004;
use URI;

while( <DATA> ) {
    chomp;
    my $uri = URI->new( $_ );

    my $r = do {
        given( $uri->scheme ) {
            when( /(?:https?|ftp)/  ) { $uri->host . $uri->path_query }
            when( 'mailto' )          { $uri->path }        
            default                   { $uri }
            }
        };

    print "$uri -> $r\n";
    }

答案 2 :(得分:1)

从cpan尝试the URI module

答案 3 :(得分:0)

我不太确定你到底想要什么。我想你要删除http://并显示一个缩短的网址。如果是这种情况,您可以执行以下操作:

#!/usr/bin/perl
use strict;
use warnings;
use 5.10.1;


my @url_list = ('http://www.foo.com/','http://www.foo.com/long_path');

for my $url (@url_list) {
    (my $short = $url) =~ s!\w+://!!;
    $short =~ s!/$!!;
    $short =~ s!^(.{15}).*$!$1...!;
    say "<a href='$url'>$short</a>";
}

输出:

<a href='http://www.foo.com/'>www.foo.com</a>
<a href='http://www.foo.com/long_path'>www.foo.com/lon...</a>

答案 4 :(得分:-1)

Perl的部分乐趣并非依赖于模块:)我设法了解以下解决方案:


#!/usr/bin/perl -w

use strict;

my @url_list = ("<a href=http://www.test.com>www.test.com</a>",
                "<a href=http://www.example.com>www.example.com</a>",
                "<a href=http://www.this.com>www.this.com</a>");

my ($protocol, $domain_name);

foreach my $url (@url_list) {
    $url =~ m|(\w+)://([^/:]+)(:\d+)?/(.*)|;
    $protocol = $1;
    $domain_name = $2;
    my ($url_part, $name_part) = split(/>/, $domain_name);
    $name_part =~ s/\<//g;
    print $protocol, "://" ,$url_part, " -> ", $name_part  , "\n";
}

这并不令人敬畏,我最终在域名中出现了一个迷路<,取代了一个替代品。要回答原始问题,您可以将LWP::SimpleHTML::LinkExtor结合使用,从网上下载和解析HTML文档。强大的组合。

**免责声明:自Ruby和Python以来,我的Perl很糟糕。向纯粹主义者道歉,要残忍你的语言。