8 |
|
|
9 |
package Message::Field::Subject; |
package Message::Field::Subject; |
10 |
use strict; |
use strict; |
11 |
use vars qw(@ISA %REG $VERSION); |
use vars qw(%DEFAULT @ISA %REG $VERSION); |
12 |
$VERSION=do{my @r=(q$Revision$=~/\d+/g);sprintf "%d."."%02d" x $#r,@r}; |
$VERSION=do{my @r=(q$Revision$=~/\d+/g);sprintf "%d."."%02d" x $#r,@r}; |
13 |
require Message::Util; |
require Message::Field::Structured; |
14 |
require Message::Field::Unstructured; |
push @ISA, q(Message::Field::Structured); |
15 |
push @ISA, q(Message::Field::Unstructured); |
|
16 |
use overload '""' => sub {shift->stringify}; |
%REG = %Message::Util::REG; |
17 |
|
$REG{news_control} = qr/^cmsg$REG{WSP}+/; |
18 |
*REG = \%Message::Util::REG; |
$REG{prefix_fwd} = qr/(?i)Fwd?/; |
19 |
$REG{re} = qr/(?:[Rr][Ee]|[Ss][Vv])\^?\[?[0-9]*\]?[:>]/; |
$REG{prefix_list} = qr/[(\[][A-Za-z0-9._-]+[\x20:-]\d+[)\]]/; |
20 |
$REG{fwd} = qr/[Ff][Ww][Dd]?:/; |
$REG{M_prefix_list} = qr/[(\[]([A-Za-z0-9._-]+)[\x20:-](\d+)[)\]]/; |
21 |
$REG{ml} = qr/[(\[][A-Za-z0-9._-]+[\x20:-][0-9]+[)\]]/; |
$REG{M_was_subject} = qr/\([Ww][Aa][Ss][:\x09\x20]$REG{FWS}(.+?)$REG{FWS}\)$REG{FWS}$/; |
22 |
$REG{M_ml} = qr/[(\[]([A-Za-z0-9._-]+)[\x20:-]([0-9]+)[)\]]/; |
$REG{message_from_subject} = qr/^$REG{FWS}(?i)Message from \S+$REG{FWS}$/; |
23 |
$REG{prefix} = qr/(?:$REG{re}|$REG{fwd}|$REG{ml})(?:$REG{FWS}(?:$REG{re}|$REG{fwd}|$REG{ml}))*/; |
if (defined $^V) { |
24 |
$REG{M_control} = qr/^cmsg$REG{FWS}([\x00-\xFF]*)$/; |
$REG{prefix_re} = qr/(?i)Re|Sv|Odp |
25 |
$REG{M_was} = qr/\([Ww][Aa][Ss]:? ([\x00-\xFF]+)\)$REG{FWS}$/; |
|\x{8FD4} ## Hen |
26 |
|
/x; |
27 |
|
$REG{prefix_advertisement} = qr/ |
28 |
|
(?i)ADV?: |
29 |
|
|[!\x{FF01}] $REG{FWS} \x{5E83}[\x{543F}\x{544A}] $REG{FWS} [!\x{FF01}] |
30 |
|
## ! kou koku ! |
31 |
|
|[!\x{FF01}] $REG{FWS} [\x{9023}\x{F99A}]\x{7D61}\x{65B9}\x{6CD5}\x{7121}\x{3057}? $REG{FWS} [!\x{FF01}] |
32 |
|
## ! ren raku hou hou nashi ! |
33 |
|
|\x{672A}\x{627F}\x{8AFE}\x{5E83}[\x{543F}\x{544A}][\x{203B}\x{0FBF}] |
34 |
|
## mi shou daku kou koku * |
35 |
|
/x; |
36 |
|
} else { |
37 |
|
$REG{prefix_re} = qr/(?i)Re|Sv/; |
38 |
|
$REG{prefix_advertisement} = qr/(?i)ADV?:/; |
39 |
|
} |
40 |
|
$REG{prefix_general} = qr/((?:$REG{prefix_re}|$REG{prefix_fwd})\^?[\[\(]?\d*[\]\)]?[:>]$REG{FWS})+/x; |
41 |
|
$REG{prefix_general_list} = qr/($REG{prefix_general}|$REG{FWS}$REG{prefix_list}$REG{FWS})+/x; |
42 |
|
|
43 |
|
## Initialize of this class -- called by constructors |
44 |
|
%DEFAULT = ( |
45 |
|
-_MEMBERS => [qw/is list_count list_name news_control was_subject/], |
46 |
|
-_METHODS => [qw/as_plain_string is list_count list_name news_control was_subject value value_type/], |
47 |
|
#encoding_after_encode |
48 |
|
#encoding_before_decode |
49 |
|
-format_news_control => 'cmsg %s', |
50 |
|
-format_prefix_fwd => 'Fwd: %s', |
51 |
|
-format_prefix_re => 'Re: %s', |
52 |
|
-format_was_subject => '%s (was: %s)', |
53 |
|
#field_param_name |
54 |
|
#field_name |
55 |
|
#field_ns |
56 |
|
#format |
57 |
|
#header_default_charset |
58 |
|
#header_default_charset_input |
59 |
|
#hook_encode_string |
60 |
|
#hook_decode_string |
61 |
|
-output_general_prefix => 1, |
62 |
|
-output_list_prefix => 1, |
63 |
|
-output_news_control => 1, |
64 |
|
-output_was_subject => 1, ## ["-"] 1*DIGIT |
65 |
|
#parse_all |
66 |
|
-parse_was_subject => 1, |
67 |
|
-use_general_prefix => 1, |
68 |
|
-use_list_prefix => 0, |
69 |
|
-use_message_from_subject => 0, |
70 |
|
-use_news_control => 1, |
71 |
|
-use_was_subject => 1, |
72 |
|
#value_type |
73 |
|
); |
74 |
|
|
75 |
=head1 CONSTRUCTORS |
=head1 CONSTRUCTORS |
76 |
|
|
77 |
The following methods construct new C<Message::Field::Subject> objects: |
The following methods construct new objects: |
78 |
|
|
79 |
=over 4 |
=over 4 |
80 |
|
|
84 |
sub _init ($;%) { |
sub _init ($;%) { |
85 |
my $self = shift; |
my $self = shift; |
86 |
my %options = @_; |
my %options = @_; |
|
my %DEFAULT = ( |
|
|
#encoding_after_encode ## Inherited |
|
|
#encoding_before_decode ## Inherited |
|
|
-format_adv => 'ADV: %s', |
|
|
-format_fwd => 'Fwd: %s', |
|
|
-format_re => 'Re: %s', |
|
|
-format_was => '%s (was: %s)', |
|
|
#hook_encode_string ## Inherited |
|
|
#hook_decode_string ## Inherited |
|
|
-prefix_cmsg => 'cmsg ', |
|
|
-regex_adv => qr/(?i)ADV:/, |
|
|
-regex_adv_check => qr/^ADV:/, |
|
|
-remove_ml_prefix => 1, |
|
|
); |
|
87 |
$self->SUPER::_init (%DEFAULT, %options); |
$self->SUPER::_init (%DEFAULT, %options); |
88 |
|
|
89 |
unless ($self->{option}->{remove_ml_prefix}) { |
#$self->{option}->{value_type}->{news_control} = ['Message::Field::UsenetControl',{}, [qw//]]; |
90 |
$REG{prefix} = qr/(?:$REG{re}|$REG{fwd})(?:$REG{FWS}(?:$REG{re}|$REG{fwd}))*/; |
$self->{option}->{value_type}->{was_subject} = ['Message::Field::Subject',{}, |
91 |
} |
[qw/format_news_control format_prefix_fwd format_prefix_re |
92 |
|
format_was_subject output_general_prefix output_list_prefix |
93 |
|
output_news_control output_was_subject parse_was_subject |
94 |
|
use_general_prefix use_list_prefix use_news_control use_was_subject/]]; |
95 |
} |
} |
96 |
|
|
97 |
=item $subject = Message::Field::Subject->new ([%options]) |
=item $subject = Message::Field::Subject->new ([%options]) |
113 |
sub parse ($$;%) { |
sub parse ($$;%) { |
114 |
my $class = shift; |
my $class = shift; |
115 |
my $self = bless {}, $class; |
my $self = bless {}, $class; |
116 |
my $field_body = shift; |
my $body = shift; |
117 |
$self->_init (@_); |
$self->_init (@_); |
118 |
if ($field_body =~ /$REG{M_control}/) { |
my $option = $self->{option}; |
119 |
$self->{is_control} = 1; ## Obsoleted control message |
if ($option->{use_news_control} && $body =~ s/$REG{news_control}//) { |
120 |
$self->{field_body} = $1; ## TODO: passes to Message::Field::Control |
$self->{news_control} = $body; |
121 |
return $self; |
return $self; |
122 |
} |
} |
123 |
my %s = &{$self->{option}->{hook_decode_string}} ($self, $field_body, |
my $value = ''; |
124 |
type => 'text'); $field_body = $s{value}; |
my %s = &{$self->{option}->{hook_decode_string}} ($self, |
125 |
$field_body =~ s{^$REG{FWS}($REG{prefix})$REG{FWS}}{ |
$body, |
126 |
my $prefix = $1; |
type => 'text', |
127 |
$self->{is_reply} = 1 if $prefix =~ /$REG{re}/; |
charset => $option->{encoding_before_decode}, |
128 |
$self->{is_foward} = 1 if $prefix =~ /$REG{fwd}/; |
); |
129 |
if ($prefix =~ /$REG{M_ml}/) { |
if ($s{charset}) { ## Convertion failed |
130 |
($self->{ml_name}, $self->{ml_count}) = ($1, $2); |
$self->{_charset} = $s{charset}; |
131 |
|
$self->{value} = $s{value}; |
132 |
|
return $self; |
133 |
|
} elsif (!$s{success}) { |
134 |
|
$self->{_charset} = $self->{option}->{header_default_charset_input}; |
135 |
|
$self->{value} = $s{value}; |
136 |
|
return $self; |
137 |
|
} |
138 |
|
$value = $s{value}; |
139 |
|
#if (!$option->{parse_all}) { |
140 |
|
# $self->{value} = $value; |
141 |
|
# return $self; |
142 |
|
#} |
143 |
|
if ($option->{use_general_prefix}) { |
144 |
|
if ($option->{use_list_prefix} && $value =~ s/^($REG{prefix_general_list})//x) { |
145 |
|
my $prefix = $1; |
146 |
|
$self->{is}->{reply} = 1 if $prefix =~ /$REG{prefix_re}/x; |
147 |
|
$self->{is}->{foward} = 1 if $prefix =~ /$REG{prefix_fwd}/x; |
148 |
|
($self->{list_name}, $self->{list_count}) = ($1, $2) |
149 |
|
if $prefix =~ /$REG{M_prefix_list}/x; |
150 |
|
} elsif ($value =~ s/^($REG{prefix_general})//x) { |
151 |
|
my $prefix = $1; |
152 |
|
$self->{is}->{reply} = 1 if $prefix =~ /$REG{prefix_re}/x; |
153 |
|
$self->{is}->{foward} = 1 if $prefix =~ /$REG{prefix_fwd}/x; |
154 |
} |
} |
155 |
'' |
} elsif ($option->{use_list_prefix} && $value =~ s/^$REG{FWS}$REG{M_prefix_list}(?:$REG{FWS}$REG{prefix_list})*$REG{FWS}//x) { |
156 |
}ex; |
($self->{list_name}, $self->{list_count}) = ($1, $2); |
157 |
$self->{is_adv} = 1 if $field_body =~ /$self->{option}->{regex_adv}/; |
} |
158 |
$field_body =~ s{$REG{FWS}$REG{M_was}}{ |
if ($option->{use_was_subject} && $value =~ s/$REG{M_was_subject}//) { |
159 |
my $was = $1; |
my $was = $1; |
160 |
if ($self->{option}->{parse_was}) { |
if ($option->{parse_was_subject}) { |
161 |
$self->{was} = Message::Field::Subject->parse ($was); |
my %option; |
162 |
$self->{was}->{option} = {%{$self->{option}}}; |
for (keys %$option) { |
163 |
## WARNING: this does not support the cases that some of option |
$option{ '-'.$_ } = Message::Util::make_clone ($option->{ $_ }); |
164 |
## values are reference to something. |
} |
165 |
|
$self->{was_subject} = ref ($self)->parse ($was, |
166 |
|
-hook_decode_string => sub { shift; (value => shift, @_) }, |
167 |
|
%option); |
168 |
} else { |
} else { |
169 |
$self->{was} = $was; |
$self->{was_subject} = $was; |
170 |
} |
} |
171 |
'' |
} |
172 |
}ex; |
if ($option->{use_message_from_subject} && $value =~ s/$REG{message_from_subject}//) { |
173 |
$self->{field_body} = $field_body; |
$self->{is}->{message_from_subject} = 1; |
174 |
|
} |
175 |
|
$self->{value} = $value; |
176 |
$self; |
$self; |
177 |
} |
} |
178 |
|
|
182 |
|
|
183 |
=over 4 |
=over 4 |
184 |
|
|
185 |
|
=cut |
186 |
|
|
187 |
|
sub value ($;$) { |
188 |
|
my $self = shift; |
189 |
|
my $v = shift; |
190 |
|
if (defined $v) { |
191 |
|
$self->{value} = $v; |
192 |
|
} |
193 |
|
$self->{value}; |
194 |
|
} |
195 |
|
|
196 |
|
sub list_name ($) { $_[0]->{list_name} } |
197 |
|
sub list_count ($) { $_[0]->{list_count} } |
198 |
|
|
199 |
=item $body = $subject->stringify |
=item $body = $subject->stringify |
200 |
|
|
201 |
Retruns subject field body as string. String is encoded |
Retruns subject field body as string. String is encoded |
204 |
=cut |
=cut |
205 |
|
|
206 |
sub stringify ($;%) { |
sub stringify ($;%) { |
207 |
my $self = shift; my %o = @_; |
my $self = shift; |
208 |
my %option = %{$self->{option}}; |
my %o = @_; my %option = %{$self->{option}}; |
209 |
for (grep {/^-/} keys %o) {$option{substr ($_, 1)} = $o{$_}} |
for (grep {/^-/} keys %o) {$option{substr ($_, 1)} = $o{$_}} |
210 |
if ($self->{is_control}) { |
if ($option{use_news_control} && $option{output_news_control} |
211 |
my $s = $self->{field_body}; |
&& $self->{news_control}) { |
212 |
$s = $option{prefix_cmsg}.$s if $s; |
my $c = $self->{news_control}; |
213 |
return $s; |
return '' unless length $c; |
214 |
} |
return sprintf $option{format_news_control}, $c; |
|
my %e = (value => $self->{field_body}); |
|
|
my $was = (ref $self->{was}? $self->{was}->as_plain_string: $self->{was}); |
|
|
if ($self->{is_reply}) { |
|
|
$e{value} = sprintf $option{format_re}, $e{value}; |
|
215 |
} |
} |
216 |
if ($self->{is_foward}) { |
if ($self->{_charset}) { |
217 |
$e{value} = sprintf $option{format_fwd}, $e{value}; |
return $self->{value}; |
218 |
} |
} else { |
219 |
if (length $was) { |
my $value = $self->{value}; |
220 |
$e{value} = sprintf $option{format_was}, $e{value} => $was; |
if ($option{use_general_prefix} && $option{output_general_prefix}) { |
221 |
} |
$value = sprintf $option{format_prefix_re}, $value if $self->{is}->{reply}; |
222 |
if ($self->{is_adv} |
$value = sprintf $option{format_prefix_fwd}, $value if $self->{is}->{foward}; |
223 |
&& $self->{field_body} !~ /$option{regex_adv_check}/) { |
} |
224 |
$e{value} = sprintf $option{format_adv}, $e{value}; |
if ($option{use_was_subject} && $option{output_was_subject} > 0) { |
225 |
|
my $was; |
226 |
|
if (ref $self->{was_subject}) { |
227 |
|
my %opt = @_; |
228 |
|
$opt{-output_was_subject} = $opt{output_was_subject} |
229 |
|
unless defined $opt{-output_was_subject}; |
230 |
|
$opt{-output_was_subject}--; |
231 |
|
$was = $self->{was_subject}->as_plain_string (%opt); |
232 |
|
} elsif (length $self->{was_subject}) { |
233 |
|
$was = $self->{was_subject}; |
234 |
|
} |
235 |
|
$value = sprintf $option{format_was_subject}, $value, $was if defined $was; |
236 |
|
} |
237 |
|
my (%e) = &{$option{hook_encode_string}} ($self, |
238 |
|
$value, |
239 |
|
charset => $option{encoding_after_encode}, |
240 |
|
current_charset => $option{internal_charset}, |
241 |
|
type => 'text', |
242 |
|
); |
243 |
|
return $e{value}; |
244 |
} |
} |
|
%e = &{$option{hook_encode_string}} ($self, $e{value}, type => 'text'); |
|
|
$e{value}; |
|
245 |
} |
} |
246 |
*as_string = \&stringify; |
*as_string = \&stringify; |
247 |
|
|
255 |
|
|
256 |
sub as_plain_string ($;%) { |
sub as_plain_string ($;%) { |
257 |
my $self = shift; |
my $self = shift; |
258 |
$self->stringify (-hook_encode_string => sub {shift; (value => shift, @_)}, @_); |
$self->stringify ( |
259 |
} |
-hook_encode_string => sub { shift; (value => shift, @_) }, |
260 |
|
@_, |
261 |
=item $text = $subject->text ([$new-text]) |
); |
|
|
|
|
Returns or set subject text (without prefixes such as "Re: "). |
|
|
|
|
|
=item $text = $subject->value |
|
|
|
|
|
An alias for C<text> method. |
|
|
|
|
|
=cut |
|
|
|
|
|
sub value ($$;$) { |
|
|
my $self = shift; |
|
|
my $ns = shift; |
|
|
if (defined $ns) { |
|
|
$self->{field_body} = $ns; |
|
|
} |
|
|
$self->{field_body}; |
|
262 |
} |
} |
|
*text = \&value; |
|
|
|
|
|
=item $subject->change ($new-subject) |
|
263 |
|
|
|
Changes subject to new text. Current subject is |
|
|
moved to I<was: >, and current I<was: > subject, if any, |
|
|
is removed. |
|
264 |
|
|
|
=cut |
|
|
|
|
|
sub change ($$;%) { |
|
|
my $self = shift; |
|
|
my $new_string = shift; |
|
|
my %option = @_; $option{-no_was} = 1 unless defined $option{-no_was}; |
|
|
$self->{was} = $self->clone (%option); |
|
|
$self->{field_body} = $new_string; |
|
|
$self->{is_adv} = 0; |
|
|
$self->{is_control} = 0; |
|
|
$self->{is_foward} = 0; |
|
|
$self->{is_reply} = 0; |
|
|
$self; |
|
|
} |
|
265 |
|
|
266 |
=item $bool = $subject->is ($attribute [=> $bool]) |
=item $bool = $subject->is ($attribute [=> $bool]) |
267 |
|
|
279 |
sub is ($@) { |
sub is ($@) { |
280 |
my $self = shift; |
my $self = shift; |
281 |
if (@_ == 1) { |
if (@_ == 1) { |
282 |
return $self->{ 'is_' . $_[0] }; |
my $query = shift; |
283 |
|
if ($query eq 'advertisement') { |
284 |
|
return $self->{value} =~ /$REG{prefix_advertisement}/x? 1:0; |
285 |
|
} else { |
286 |
|
return $self->{is}->{ $_[0] }; |
287 |
|
} |
288 |
} |
} |
289 |
while (my ($name, $value) = splice (@_, 0, 2)) { |
while (my ($name, $value) = splice (@_, 0, 2)) { |
290 |
$self->{ 'is_' . $name } = $value; |
$self->{is}->{ $name } = $value; |
291 |
} |
} |
292 |
} |
} |
293 |
|
|
294 |
=item $old_subject = $subject->was |
=item $old_subject = $subject->was_subject |
295 |
|
|
296 |
Returns I<was: > subject. |
Returns I<was: > subject. |
297 |
|
|
298 |
=cut |
=cut |
299 |
|
|
300 |
sub was ($) { |
sub was_subject ($) { |
301 |
my $self = shift; |
my $self = shift; |
302 |
if (ref $self->{was}) { |
$self->{was_subject} = $self->_parse_all (was => $self->{was_subject}) |
303 |
# |
if $self->{option}->{parse_all}; |
304 |
} elsif ($self->{was}) { |
$self->{was_subject}; |
305 |
$self->{was} = Message::Field::Subject->parse ($self->{was}); |
} |
306 |
$self->{was}->{option} = {%{$self->{option}}}; |
|
307 |
} else { |
sub news_control ($) { |
308 |
$self->{was} = new Message::Field::Subject; |
my $self = shift; |
309 |
$self->{was}->{option} = {%{$self->{option}}}; |
$self->{news_control} = $self->_parse_all (was => $self->{news_control}) |
310 |
} |
if $self->{option}->{parse_all}; |
311 |
$self->{was}; |
$self->{news_control}; |
312 |
} |
} |
313 |
|
|
314 |
=item $clone = $subject->clone () |
=item $clone = $subject->clone () |
317 |
|
|
318 |
=cut |
=cut |
319 |
|
|
320 |
sub clone ($;%) { |
## Inherited |
|
my $self = shift; my %option = @_; |
|
|
my $clone = $self->SUPER::clone; |
|
|
for (grep {/^is_/} keys %{$self}) { |
|
|
$clone->{$_} = $self->{$_}; |
|
|
} |
|
|
if (!$option{-no_was} && $self->{was}) { |
|
|
if (ref $self->{was}) { |
|
|
$clone->{was} = $self->{was}->clone; |
|
|
} else { |
|
|
$clone->{was} = $self->{was}; |
|
|
} |
|
|
} |
|
|
$clone; |
|
|
} |
|
|
|
|
|
=head1 EXAMPLE |
|
|
|
|
|
my $subject = parse Message::Field::Subject 'Re: cool message'; |
|
|
$subject->change (q{What's "cool"?}); |
|
|
print $subject; # What's "cool"? (was: Re: cool message) |
|
321 |
|
|
322 |
=head1 LICENSE |
=head1 LICENSE |
323 |
|
|