home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / xampp / xampp-perl-addon-1.4.9-installer.exe / Address.pm < prev    next >
Encoding:
Perl POD Document  |  2004-03-29  |  8.9 KB  |  439 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.62";
  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.  my $atext = '[\-\w !#$%&\'*+/=?^`{|}~]';
  246.  
  247.  foreach $me (@_) {
  248.    my($phrase,$addr,$comment) = @{$me};
  249.    my @tmp = ();
  250.  
  251.    if(defined $phrase && length($phrase)) {
  252.      push @tmp, $phrase =~ /^(?:\s*$atext\s*)+$/ ? $phrase
  253.               : $phrase =~ /(?<!\\)"/            ? $phrase
  254.               :                                    qq("$phrase");
  255.  
  256.      push(@tmp, "<" . $addr . ">") if(defined $addr && length($addr));
  257.    }
  258.    else {
  259.     push(@tmp, $addr) if(defined $addr && length($addr));
  260.    }
  261.    if(defined($comment) && $comment =~ /\S/) {
  262.     $comment =~ s/^\s*\(?/(/;
  263.     $comment =~ s/\)?\s*$/)/;
  264.    }
  265.    push(@tmp, $comment) if(defined $comment && length($comment));
  266.    push(@fmts, join(" ", @tmp)) if(scalar(@tmp));
  267.  }
  268.  
  269.  return join(", ", @fmts);
  270. }
  271.  
  272.  
  273. sub name 
  274. {
  275.     my $me = shift;
  276.     my $phrase = $me->phrase;
  277.     my $addr = $me->address;
  278.     
  279.     $phrase  = $me->comment unless(defined($phrase) && length($phrase));
  280.     my $name = $me->_extract_name($phrase);
  281.     
  282.     # first.last@domain address
  283.     if($name eq '' && $addr =~ /([^\%\.\@_]+([\._][^\%\.\@_]+)+)[\@\%]/o)
  284.     {
  285.     ($name = $1) =~ s/[\._]+/ /go;
  286.     $name = _extract_name($name);
  287.     }
  288.     
  289.     if($name eq '' && $addr =~ m#/g=#oi)    
  290.     # X400 style address
  291.     {
  292.     my ($f) = $addr =~ m#g=([^/]*)#oi;
  293.     my ($l) = $addr =~ m#s=([^/]*)#io;
  294.     
  295.     $name = _extract_name($f . " " . $l);
  296.     }   
  297.        
  298.        return length($name) ? $name : undef;
  299. }
  300.  
  301.  
  302. sub host {
  303.  my $me = shift;
  304.  my $addr = $me->address || '';
  305.  my $i = rindex($addr,'@');
  306.  
  307.  my $host = ($i >= 0) ? substr($addr,$i+1) : undef;
  308.  
  309.  return $host;
  310. }
  311.  
  312.  
  313. sub user {
  314.  my $me = shift;
  315.  my $addr = $me->address;
  316.  my $i = index($addr,'@');
  317.  
  318.  my $user = ($i >= 0) ? substr($addr,0,$i) : $addr;
  319.  
  320.  return $user;
  321. }
  322.  
  323.  
  324. sub path {
  325.  return ();
  326. }
  327.  
  328.  
  329. sub canon {
  330.  my $me = shift;
  331.  return ($me->host, $me->user, $me->path);
  332. }
  333.  
  334. 1;
  335.  
  336.  
  337. __END__
  338.  
  339. =head1 NAME
  340.  
  341. Mail::Address - Parse mail addresses
  342.  
  343. =head1 SYNOPSIS
  344.  
  345.     use Mail::Address;
  346.  
  347.     my @addrs = Mail::Address->parse($line);
  348.  
  349.     foreach $addr (@addrs) {
  350.     print $addr->format,"\n";
  351.     }
  352.  
  353. =head1 DESCRIPTION
  354.  
  355. C<Mail::Address> extracts and manipulates RFC822 compilant email
  356. addresses. As well as being able to create C<Mail::Address> objects
  357. in the normal manner, C<Mail::Address> can extract addresses from
  358. the To and Cc lines found in an email message.
  359.  
  360. =head1 CONSTRUCTORS
  361.  
  362. =over 4
  363.  
  364. =item new( PHRASE,  ADDRESS, [ COMMENT ])
  365.  
  366.  Mail::Address->new("Perl5 Porters", "perl5-porters@africa.nicoh.com");
  367.  
  368. Create a new C<Mail::Address> object which represents an address with the
  369. elements given. In a message these 3 elements would be seen like:
  370.  
  371.  PHRASE <ADDRESS> (COMMENT)
  372.  ADDRESS (COMMENT)
  373.  
  374. =item parse( LINE )
  375.  
  376.  Mail::Address->parse($line);
  377.  
  378. Parse the given line a return a list of extracted C<Mail::Address> objects.
  379. The line would normally be one taken from a To,Cc or Bcc line in a message
  380.  
  381. =back
  382.  
  383. =head1 METHODS
  384.  
  385. =over 4
  386.  
  387. =item phrase ()
  388.  
  389. Return the phrase part of the object.
  390.  
  391. =item address ()
  392.  
  393. Return the address part of the object.
  394.  
  395. =item comment ()
  396.  
  397. Return the comment part of the object
  398.  
  399. =item format ()
  400.  
  401. Return a string representing the address in a suitable form to be placed
  402. on a To,Cc or Bcc line of a message
  403.  
  404. =item name ()
  405.  
  406. Using the information contained within the object attempt to identify what
  407. the person or groups name is
  408.  
  409. =item host ()
  410.  
  411. Return the address excluding the user id and '@'
  412.  
  413. =item user ()
  414.  
  415. Return the address excluding the '@' and the mail domain
  416.  
  417. =item path ()
  418.  
  419. Unimplemented yet but should return the UUCP path for the message
  420.  
  421. =item canon ()
  422.  
  423. Unimplemented yet but should return the UUCP canon for the message
  424.  
  425. =back
  426.  
  427. =head1 AUTHOR
  428.  
  429. Graham Barr.  Maintained by Mark Overmeer <mailtools@overmeer.net>
  430.  
  431. =head1 COPYRIGHT
  432.  
  433. Copyright (c) 2002-2003 Mark Overmeer, 1995-2001 Graham Barr. All rights
  434. reserved. This program is free software; you can redistribute it and/or
  435. modify it under the same terms as Perl itself.
  436.  
  437. =cut
  438.  
  439.