our
$VERSION
=
'2.22'
;
}
sub
Version {
our
$VERSION
}
sub
_extract_name
{
my
$self
=
@_
&&
ref
$_
[0] ?
shift
:
undef
;
local
$_
=
shift
or
return
''
;
return
''
if
m/\=\?.*?\?\=/;
s/^\s+//;
s/\s+$//;
s/\s+/ /;
return
""
if
/^[\d ]+$/;
s/^\((.*)\)$/$1/;
s/^
"(.*)"
$/$1/;
s/\(.*?\)//g;
s/\\//g;
s/^
"(.*)"
$/$1/;
s/^([^\s]+) ?, ?(.*)$/$2 $1/;
s/,.*//;
unless
( m/[A-Z]/ && m/[a-z]/ )
{
s/\b(\w+)/\L\u$1/igo;
s/\bMc(\w)/Mc\u$1/igo;
s/\bo
'(\w)/O'
\u$1/igo;
s/\b(x*(ix)?v*(iv)?i*)\b/\U$1/igo;
}
s/\[[^\]]*\]//g;
s/(^[\s
'"]+|[\s'
"]+$)//g;
s/\s{2,}/ /g;
$_
;
}
sub
_tokenise
{
local
$_
=
join
','
,
@_
;
my
(
@words
,
$snippet
,
$field
);
s/\A\s+//;
s/[\r\n]+/ /g;
while
(
$_
ne
''
)
{
$field
=
''
;
if
(s/^\s*\(/(/ )
{
my
$depth
= 0;
PAREN:
while
(s/^(\(([^\(\)\\]|\\.)*)//)
{
$field
.= $1;
$depth
++;
while
(s/^(([^\(\)\\]|\\.)*\)\s*)//)
{
$field
.= $1;
last
PAREN
unless
--
$depth
;
$field
.= $1
if
s/^(([^\(\)\\]|\\.)+)//;
}
}
carp
"Unmatched () '$field' '$_'"
if
$depth
;
$field
=~ s/\s+\Z//;
push
@words
,
$field
;
next
;
}
if
( s/^(
"(?:[^"
\\]+|\\.)*
")\s*// # "
..."
|| s/^(\[(?:[^\]\\]+|\\.)*\])\s*//
|| s/^([^\s()<>\@,;:\\".[\]]+)\s*//
|| s/^([()<>\@,;:\\".[\]])\s*//
)
{
push
@words
, $1;
next
;
}
croak
"Unrecognised line: $_"
;
}
push
@words
,
","
;
\
@words
;
}
sub
_find_next
{
my
(
$idx
,
$tokens
,
$len
) =
@_
;
while
(
$idx
<
$len
)
{
my
$c
=
$tokens
->[
$idx
];
return
$c
if
$c
eq
','
||
$c
eq
';'
||
$c
eq
'<'
;
$idx
++;
}
""
;
}
sub
_complete
{
my
(
$class
,
$phrase
,
$address
,
$comment
) =
@_
;
@$phrase
||
@$comment
||
@$address
or
return
undef
;
my
$o
=
$class
->new(
join
(
" "
,
@$phrase
),
join
(
""
,
@$address
),
join
(
" "
,
@$comment
));
@$phrase
=
@$address
=
@$comment
= ();
$o
;
}
sub
new(@)
{
my
$class
=
shift
;
bless
[
@_
],
$class
;
}
sub
parse(@)
{
my
$class
=
shift
;
my
@line
=
grep
{
defined
}
@_
;
my
$line
=
join
''
,
@line
;
my
(
@phrase
,
@comment
,
@address
,
@objs
);
my
(
$depth
,
$idx
) = (0, 0);
my
$tokens
= _tokenise
@line
;
my
$len
=
@$tokens
;
my
$next
= _find_next
$idx
,
$tokens
,
$len
;
local
$_
;
for
(
my
$idx
= 0;
$idx
<
$len
;
$idx
++)
{
$_
=
$tokens
->[
$idx
];
if
(
substr
(
$_
,0,1) eq
'('
) {
push
@comment
,
$_
}
elsif
(
$_
eq
'<'
) {
$depth
++ }
elsif
(
$_
eq
'>'
) {
$depth
--
if
$depth
}
elsif
(
$_
eq
','
||
$_
eq
';'
)
{
warn
"Unmatched '<>' in $line"
if
$depth
;
my
$o
=
$class
->_complete(\
@phrase
, \
@address
, \
@comment
);
push
@objs
,
$o
if
defined
$o
;
$depth
= 0;
$next
= _find_next
$idx
+1,
$tokens
,
$len
;
}
elsif
(
$depth
) {
push
@address
,
$_
}
elsif
(
$next
eq
'<'
) {
push
@phrase
,
$_
}
elsif
( /^[.\@:;]$/ || !
@address
||
$address
[-1] =~ /^[.\@:;]$/ )
{
push
@address
,
$_
}
else
{
warn
"Unmatched '<>' in $line"
if
$depth
;
my
$o
=
$class
->_complete(\
@phrase
, \
@address
, \
@comment
);
push
@objs
,
$o
if
defined
$o
;
$depth
= 0;
push
@address
,
$_
;
}
}
@objs
;
}
sub
phrase {
shift
->set_or_get(0,
@_
) }
sub
address {
shift
->set_or_get(1,
@_
) }
sub
comment {
shift
->set_or_get(2,
@_
) }
sub
set_or_get($)
{
my
(
$self
,
$i
) = (
shift
,
shift
);
@_
or
return
$self
->[
$i
];
my
$val
=
$self
->[
$i
];
$self
->[
$i
] =
shift
if
@_
;
$val
;
}
my
$atext
=
'[\-\w !#$%&\'*+/=?^`{|}~]'
;
sub
format
{
my
@addrs
;
foreach
(
@_
)
{
my
(
$phrase
,
$email
,
$comment
) =
@$_
;
my
@addr
;
if
(
defined
$phrase
&&
length
$phrase
)
{
push
@addr
,
$phrase
=~ /^(?:\s
*$atext
\s*)+$/o ?
$phrase
:
$phrase
=~ /(?<!\\)"/ ?
$phrase
:
qq("$phrase")
;
push
@addr
,
"<$email>"
if
defined
$email
&&
length
$email
;
}
elsif
(
defined
$email
&&
length
$email
)
{
push
@addr
,
$email
;
}
if
(
defined
$comment
&&
$comment
=~ /\S/)
{
$comment
=~ s/^\s*\(?/(/;
$comment
=~ s/\)?\s*$/)/;
}
push
@addr
,
$comment
if
defined
$comment
&&
length
$comment
;
push
@addrs
,
join
(
" "
,
@addr
)
if
@addr
;
}
join
", "
,
@addrs
;
}
sub
name
{
my
$self
=
shift
;
my
$phrase
=
$self
->phrase;
my
$addr
=
$self
->address;
$phrase
=
$self
->comment
unless
defined
$phrase
&&
length
$phrase
;
my
$name
=
$self
->_extract_name(
$phrase
);
if
(
$name
eq
''
&&
$addr
=~ /([^\%\.\
@_
]+([\._][^\%\.\
@_
]+)+)[\@\%]/)
{ (
$name
= $1) =~ s/[\._]+/ /g;
$name
= _extract_name
$name
;
}
if
(
$name
eq
''
&&
$addr
=~ m
{
my
(
$f
) =
$addr
=~ m
my
(
$l
) =
$addr
=~ m
$name
= _extract_name
"$f $l"
;
}
length
$name
?
$name
:
undef
;
}
sub
host
{
my
$addr
=
shift
->address ||
''
;
my
$i
=
rindex
$addr
,
'@'
;
$i
>= 0 ?
substr
(
$addr
,
$i
+1) :
undef
;
}
sub
user
{
my
$addr
=
shift
->address ||
''
;
my
$i
=
rindex
$addr
,
'@'
;
$i
>= 0 ?
substr
(
$addr
,0,
$i
) :
$addr
;
}
1;