home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / Address.pm < prev    next >
Encoding:
Perl POD Document  |  2003-09-22  |  8.3 KB  |  434 lines

  1. # Mail::Address.pm
  2. #
  3. # Copyright (c) 1995-2001 Graham Barr <gbarr@pobox.com>.  All rights reserved.
  4. # Copyright (c) 2002-2003 Mark Overmeer <mailtools@overmeer.net>
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the same terms as Perl itself.
  7.  
  8. package Mail::Address;
  9. use strict;
  10.  
  11. use Carp;
  12. use vars qw($VERSION);
  13. use locale;
  14.  
  15. $VERSION = "1.60";
  16. sub Version { $VERSION }
  17.  
  18. #
  19. # given a comment, attempt to extract a person's name
  20. #
  21.  
  22. sub _extract_name
  23. {
  24.     # This function can be called as method as well
  25.     my $self = @_ && ref $_[0] ? shift : undef;
  26.  
  27.     local $_ = shift or return '';
  28.     
  29.     # Bug in unicode \U, perl 5.8.0 breaks when casing utf8 in regex
  30.     if($] eq 5.008)
  31.     {   require utf8;
  32.         eval 'utf8::downgrade($_)';
  33.     }
  34.  
  35.     # trim whitespace
  36.     s/^\s+//;
  37.     s/\s+$//;
  38.     s/\s+/ /;
  39.  
  40.     # Disregard numeric names (e.g. 123456.1234@compuserve.com)
  41.     return "" if /^[\d ]+$/;
  42.  
  43.     # remove outermost parenthesis
  44.     s/^\((.*)\)$/$1/g;
  45.  
  46.     # remove outer quotation marks
  47.     s/^"|"$//g;
  48.  
  49.     # remove embedded comments
  50.     s/\(.*\)//g;
  51.  
  52.     # reverse "Last, First M." if applicable
  53.     s/^([^\s]+) ?, ?(.*)$/$2 $1/;
  54.     s/,.*//;
  55.  
  56.     # Change casing only when the name contains only upper or only
  57.     # lower cased characters.
  58.     unless( m/[A-Z]/ && m/[a-z]/ )
  59.     {   # Set the case of the name to first char upper rest lower
  60.         # Upcase first letter on name
  61.         s/\b(\w+)/\L\u$1/igo;
  62.  
  63.         # Scottish names such as 'McLeod'
  64.         s/\bMc(\w)/Mc\u$1/igo;
  65.  
  66.         # Irish names such as 'O'Malley, O'Reilly'
  67.         s/\bo'(\w)/O'\u$1/igo;
  68.  
  69.         # Roman numerals, eg 'Level III Support'
  70.         s/\b(x*(ix)?v*(iv)?i*)\b/\U$1/igo; 
  71.     }
  72.  
  73.     # some cleanup
  74.     s/\[[^\]]*\]//g;
  75.     s/(^[\s'"]+|[\s'"]+$)//g;
  76.     s/\s{2,}/ /g;
  77.  
  78.     return $_;
  79. }
  80.  
  81. sub _tokenise {
  82.  local($_) = join(',', @_);
  83.  my(@words,$snippet,$field);
  84.  
  85.  s/\A\s+//;
  86.  s/[\r\n]+/ /g;
  87.  
  88.  while ($_ ne '')
  89.   {
  90.    $field = '';
  91.    if( s/^\s*\(/(/ )    # (...)
  92.     {
  93.      my $depth = 0;
  94.  
  95.      PAREN: while(s/^(\(([^\(\)\\]|\\.)*)//)
  96.       {
  97.        $field .= $1;
  98.        $depth++;
  99.        while(s/^(([^\(\)\\]|\\.)*\)\s*)//)
  100.         {
  101.          $field .= $1;
  102.          last PAREN unless --$depth;
  103.      $field .= $1 if s/^(([^\(\)\\]|\\.)+)//;
  104.         }
  105.       }
  106.  
  107.      carp "Unmatched () '$field' '$_'"
  108.         if $depth;
  109.  
  110.      $field =~ s/\s+\Z//;
  111.      push(@words, $field);
  112.  
  113.      next;
  114.     }
  115.  
  116.       s/^("([^"\\]|\\.)*")\s*//       # "..."
  117.    || s/^(\[([^\]\\]|\\.)*\])\s*//    # [...]
  118.    || s/^([^\s\Q()<>\@,;:\\".[]\E]+)\s*//
  119.    || s/^([\Q()<>\@,;:\\".[]\E])\s*//
  120.      and do { push(@words, $1); next; };
  121.  
  122.    croak "Unrecognised line: $_";
  123.   }
  124.  
  125.  push(@words, ",");
  126.  
  127.  \@words;
  128. }
  129.  
  130. sub _find_next {
  131.  my $idx = shift;
  132.  my $tokens = shift;
  133.  my $len = shift;
  134.  while($idx < $len) {
  135.    my $c = $tokens->[$idx];
  136.    return $c if($c eq "," || $c eq "<");
  137.    $idx++;
  138.  }
  139.  return "";
  140. }
  141.  
  142. sub _complete {
  143.  my $pkg = shift;
  144.  my $phrase = shift;
  145.  my $address = shift;
  146.  my $comment = shift;
  147.  my $o = undef;
  148.  
  149.  if(@{$phrase} || @{$comment} || @{$address}) {
  150.   $o = $pkg->new(join(" ",@{$phrase}), 
  151.                  join("", @{$address}),
  152.                  join(" ",@{$comment}));
  153.   @{$phrase} = ();
  154.   @{$address} = ();
  155.   @{$comment} = ();
  156.  }
  157.  
  158.  return $o;
  159. }
  160.  
  161.  
  162. sub new {
  163.  my $pkg = shift;
  164.  my $me = bless [@_], $pkg;
  165.  return $me;
  166. }
  167.  
  168.  
  169. sub parse {
  170.  my $pkg = shift;
  171.  my @line    = grep { defined $_} @_;
  172.  my $line    = join '', @line;
  173.  
  174.  local $_;
  175.  
  176.  my @phrase  = ();
  177.  my @comment = ();
  178.  my @address = ();
  179.  my @objs    = ();
  180.  my $depth   = 0;
  181.  my $idx     = 0;
  182.  my $tokens  = _tokenise(@line);
  183.  my $len     = @$tokens;
  184.  my $next    = _find_next($idx,$tokens,$len);
  185.  
  186.  for( ; $idx < $len ; $idx++) {
  187.   $_ = $tokens->[$idx];
  188.  
  189.   if(substr($_,0,1) eq "(") {
  190.    push(@comment,$_);
  191.   }
  192.   elsif($_ eq '<') {
  193.    $depth++;
  194.   }
  195.   elsif($_ eq '>') {
  196.    $depth-- if $depth;
  197.   }
  198.   elsif($_ eq ',') {
  199.    warn "Unmatched '<>' in $line" if($depth);
  200.    my $o = _complete($pkg,\@phrase, \@address, \@comment);
  201.    push(@objs, $o) if(defined $o);
  202.    $depth = 0;
  203.    $next = _find_next($idx+1,$tokens,$len);
  204.   }
  205.   elsif($depth) {
  206.    push(@address,$_);
  207.   }
  208.   elsif($next eq "<") {
  209.    push(@phrase,$_);
  210.   }
  211.   elsif( /\A[\Q.\@:;\E]\Z/ || !@address || $address[-1] =~ /\A[\Q.\@:;\E]\Z/) {
  212.    push(@address,$_);
  213.   }
  214.   else {
  215.    warn "Unmatched '<>' in $line" if($depth);
  216.    my $o = _complete($pkg,\@phrase, \@address, \@comment);
  217.    push(@objs, $o) if(defined $o);
  218.    $depth = 0;
  219.    push(@address,$_);
  220.   }
  221.  }
  222.  @objs;
  223. }
  224.  
  225. sub set_or_get {
  226.  my $me = shift;
  227.  my $i = shift;
  228.  my $val = $me->[$i];
  229.  
  230.  $me->[$i] = shift if(@_);
  231.  
  232.  $val;
  233. }
  234.  
  235.  
  236. sub phrase  { set_or_get(shift,0,@_) }
  237. sub address { set_or_get(shift,1,@_) }
  238. sub comment { set_or_get(shift,2,@_) }
  239.  
  240.  
  241. sub format {
  242.  my @fmts = ();
  243.  my $me;
  244.  
  245.  foreach $me (@_) {
  246.    my($phrase,$addr,$comment) = @{$me};
  247.    my @tmp = ();
  248.  
  249.    if(defined $phrase && length($phrase)) {
  250.     push(@tmp, $phrase);
  251.     push(@tmp, "<" . $addr . ">") if(defined $addr && length($addr));
  252.    }
  253.    else {
  254.     push(@tmp, $addr) if(defined $addr && length($addr));
  255.    }
  256.    if(defined($comment) && $comment =~ /\S/) {
  257.     $comment =~ s/^\s*\(?/(/;
  258.     $comment =~ s/\)?\s*$/)/;
  259.    }
  260.    push(@tmp, $comment) if(defined $comment && length($comment));
  261.    push(@fmts, join(" ", @tmp)) if(scalar(@tmp));
  262.  }
  263.  
  264.  return join(", ", @fmts);
  265. }
  266.  
  267.  
  268. sub name 
  269. {
  270.     my $me = shift;
  271.     my $phrase = $me->phrase;
  272.     my $addr = $me->address;
  273.     
  274.     $phrase  = $me->comment unless(defined($phrase) && length($phrase));
  275.     my $name = $me->_extract_name($phrase);
  276.     
  277.     # first.last@domain address
  278.     if($name eq '' && $addr =~ /([^\%\.\@_]+([\._][^\%\.\@_]+)+)[\@\%]/o)
  279.     {
  280.     ($name = $1) =~ s/[\._]+/ /go;
  281.     $name = _extract_name($name);
  282.     }
  283.     
  284.     if($name eq '' && $addr =~ m#/g=#oi)    
  285.     # X400 style address
  286.     {
  287.     my ($f) = $addr =~ m#g=([^/]*)#oi;
  288.     my ($l) = $addr =~ m#s=([^/]*)#io;
  289.     
  290.     $name = _extract_name($f . " " . $l);
  291.     }   
  292.        
  293.        return length($name) ? $name : undef;
  294. }
  295.  
  296.  
  297. sub host {
  298.  my $me = shift;
  299.  my $addr = $me->address;
  300.  my $i = rindex($addr,'@');
  301.  
  302.  my $host = ($i >= 0) ? substr($addr,$i+1) : undef;
  303.  
  304.  return $host;
  305. }
  306.  
  307.  
  308. sub user {
  309.  my $me = shift;
  310.  my $addr = $me->address;
  311.  my $i = index($addr,'@');
  312.  
  313.  my $user = ($i >= 0) ? substr($addr,0,$i) : $addr;
  314.  
  315.  return $user;
  316. }
  317.  
  318.  
  319. sub path {
  320.  return ();
  321. }
  322.  
  323.  
  324. sub canon {
  325.  my $me = shift;
  326.  return ($me->host, $me->user, $me->path);
  327. }
  328.  
  329. 1;
  330.  
  331.  
  332. __END__
  333.  
  334. =head1 NAME
  335.  
  336. Mail::Address - Parse mail addresses
  337.  
  338. =head1 SYNOPSIS
  339.  
  340.     use Mail::Address;
  341.     
  342.     my @addrs = Mail::Address->parse($line);
  343.     
  344.     foreach $addr (@addrs) {
  345.     print $addr->format,"\n";
  346.     }
  347.  
  348. =head1 DESCRIPTION
  349.  
  350. C<Mail::Address> extracts and manipulates RFC822 compilant email
  351. addresses. As well as being able to create C<Mail::Address> objects
  352. in the normal manner, C<Mail::Address> can extract addresses from
  353. the To and Cc lines found in an email message.
  354.  
  355. =head1 CONSTRUCTORS
  356.  
  357. =over 4
  358.  
  359. =item new( PHRASE,  ADDRESS, [ COMMENT ])
  360.  
  361.  Mail::Address->new("Perl5 Porters", "perl5-porters@africa.nicoh.com");
  362.  
  363. Create a new C<Mail::Address> object which represents an address with the
  364. elements given. In a message these 3 elements would be seen like:
  365.  
  366.  PHRASE <ADDRESS> (COMMENT)
  367.  ADDRESS (COMMENT)
  368.  
  369. =item parse( LINE )
  370.  
  371.  Mail::Address->parse($line);
  372.  
  373. Parse the given line a return a list of extracted C<Mail::Address> objects.
  374. The line would normally be one taken from a To,Cc or Bcc line in a message
  375.  
  376. =back
  377.  
  378. =head1 METHODS
  379.  
  380. =over 4
  381.  
  382. =item phrase ()
  383.  
  384. Return the phrase part of the object.
  385.  
  386. =item address ()
  387.  
  388. Return the address part of the object.
  389.  
  390. =item comment ()
  391.  
  392. Return the comment part of the object
  393.  
  394. =item format ()
  395.  
  396. Return a string representing the address in a suitable form to be placed
  397. on a To,Cc or Bcc line of a message
  398.  
  399. =item name ()
  400.  
  401. Using the information contained within the object attempt to identify what
  402. the person or groups name is
  403.  
  404. =item host ()
  405.  
  406. Return the address excluding the user id and '@'
  407.  
  408. =item user ()
  409.  
  410. Return the address excluding the '@' and the mail domain
  411.  
  412. =item path ()
  413.  
  414. Unimplemented yet but should return the UUCP path for the message
  415.  
  416. =item canon ()
  417.  
  418. Unimplemented yet but should return the UUCP canon for the message
  419.  
  420. =back
  421.  
  422. =head1 AUTHOR
  423.  
  424. Graham Barr.  Maintained by Mark Overmeer <mailtools@overmeer.net>
  425.  
  426. =head1 COPYRIGHT
  427.  
  428. Copyright (c) 2002-2003 Mark Overmeer, 1995-2001 Graham Barr. All rights
  429. reserved. This program is free software; you can redistribute it and/or
  430. modify it under the same terms as Perl itself.
  431.  
  432. =cut
  433.  
  434.