| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
2
|
|
|
2
|
|
403210
|
use v5.10.1; |
|
|
2
|
|
|
|
|
10
|
|
|
|
2
|
|
|
|
|
283
|
|
|
2
|
2
|
|
|
2
|
|
15
|
use strict; |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
92
|
|
|
3
|
2
|
|
|
2
|
|
16
|
use warnings; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
476
|
|
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
package WWW::ArsenalFC::TicketInformation; |
|
6
|
|
|
|
|
|
|
{ |
|
7
|
|
|
|
|
|
|
$WWW::ArsenalFC::TicketInformation::VERSION = '1.123160'; |
|
8
|
|
|
|
|
|
|
} |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
# ABSTRACT: Get Arsenal FC ticket information for forthcoming matches |
|
11
|
|
|
|
|
|
|
|
|
12
|
2
|
|
|
2
|
|
2512
|
use WWW::ArsenalFC::TicketInformation::Match; |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
24
|
|
|
13
|
2
|
|
|
2
|
|
317
|
use WWW::ArsenalFC::TicketInformation::Match::Availability; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
16
|
|
|
14
|
2
|
|
|
2
|
|
2763
|
use WWW::ArsenalFC::TicketInformation::Category; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
26
|
|
|
15
|
|
|
|
|
|
|
|
|
16
|
2
|
|
|
2
|
|
3873
|
use LWP::Simple (); |
|
|
2
|
|
|
|
|
411756
|
|
|
|
2
|
|
|
|
|
68
|
|
|
17
|
2
|
|
|
2
|
|
2624
|
use HTML::TreeBuilder::XPath (); |
|
|
2
|
|
|
|
|
305079
|
|
|
|
2
|
|
|
|
|
64
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# the URL on Arsenal.com |
|
20
|
2
|
|
|
2
|
|
30
|
use constant URL => 'http://www.arsenal.com/membership/buy-tickets'; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
19969
|
|
|
21
|
|
|
|
|
|
|
|
|
22
|
2
|
|
|
|
|
67
|
use Object::Tiny qw{ |
|
23
|
|
|
|
|
|
|
categories |
|
24
|
|
|
|
|
|
|
matches |
|
25
|
2
|
|
|
2
|
|
26
|
}; |
|
|
2
|
|
|
|
|
4
|
|
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub fetch { |
|
28
|
2
|
|
|
2
|
1
|
727007
|
my ($self) = @_; |
|
29
|
|
|
|
|
|
|
|
|
30
|
2
|
|
|
|
|
14
|
$self->_fetch_categories(); |
|
31
|
2
|
|
|
|
|
12
|
$self->_fetch_matches(); |
|
32
|
|
|
|
|
|
|
} |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
sub _fetch_categories { |
|
35
|
2
|
|
|
2
|
|
6
|
my ($self) = @_; |
|
36
|
|
|
|
|
|
|
|
|
37
|
2
|
|
|
|
|
12
|
my $tree = $self->_get_tree(); |
|
38
|
2
|
|
|
|
|
5
|
my @categories; |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# get the categories table |
|
41
|
2
|
|
|
|
|
44
|
my $rows = $tree->findnodes('//table[@summary="Match categories"]//tr'); |
|
42
|
|
|
|
|
|
|
|
|
43
|
2
|
|
|
|
|
589852
|
my %categories_hash = (); |
|
44
|
2
|
|
|
|
|
15
|
for ( my $i = 1 ; $i < $rows->size() ; $i++ ) { |
|
45
|
10
|
|
|
|
|
129
|
my $row = $rows->[$i]; |
|
46
|
|
|
|
|
|
|
|
|
47
|
10
|
|
|
|
|
38
|
my $category = WWW::ArsenalFC::TicketInformation::Category->new( |
|
48
|
|
|
|
|
|
|
date_string => $row->findvalue('td[1]'), |
|
49
|
|
|
|
|
|
|
opposition => $row->findvalue('td[2]'), |
|
50
|
|
|
|
|
|
|
category => $row->findvalue('td[3]'), |
|
51
|
|
|
|
|
|
|
); |
|
52
|
|
|
|
|
|
|
|
|
53
|
10
|
|
|
|
|
20974
|
push @categories, $category; |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# used to assign the category to a match later |
|
56
|
10
|
|
|
|
|
343
|
my $category_key = |
|
57
|
|
|
|
|
|
|
sprintf( "%s:%s", $category->opposition, $category->date ); |
|
58
|
10
|
|
|
|
|
246
|
$categories_hash{$category_key} = $category->category; |
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
|
|
61
|
2
|
|
|
|
|
25
|
$self->{categories} = \@categories; |
|
62
|
2
|
|
|
|
|
12
|
$self->{categories_hash} = \%categories_hash; |
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
sub _fetch_matches { |
|
66
|
2
|
|
|
2
|
|
5
|
my ($self) = @_; |
|
67
|
|
|
|
|
|
|
|
|
68
|
2
|
|
|
|
|
9
|
my $tree = $self->_get_tree(); |
|
69
|
2
|
|
|
|
|
5
|
my @matches; |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# get the table and loop over every 3 rows, as these |
|
72
|
|
|
|
|
|
|
# contain the matches |
|
73
|
|
|
|
|
|
|
# the second and third rows contain data on who can purchase tickets, if |
|
74
|
|
|
|
|
|
|
# its not yet sold out or on the exchange. |
|
75
|
2
|
|
|
|
|
12
|
my $rows = $tree->findnodes('//table[@id="member-tickets"]/tr'); |
|
76
|
2
|
|
|
|
|
489182
|
for ( my $i = 0 ; $i < $rows->size() ; $i += 3 ) { |
|
77
|
9
|
|
|
|
|
67
|
my %match = (); |
|
78
|
9
|
|
|
|
|
17
|
my $row = $rows->[$i]; |
|
79
|
|
|
|
|
|
|
|
|
80
|
9
|
|
|
|
|
41
|
$match{fixture} = _trimWhitespace( $row->findvalue('td[2]/p[1]') ); |
|
81
|
9
|
|
|
|
|
44
|
$match{competition} = _trimWhitespace( $row->findvalue('td[2]/p[2]') ); |
|
82
|
9
|
|
|
|
|
33
|
$match{datetime_string} = |
|
83
|
|
|
|
|
|
|
_trimWhitespace( $row->findvalue('td[2]/p[3]') ); |
|
84
|
9
|
|
|
|
|
44
|
$match{hospitality} = $row->exists( |
|
85
|
|
|
|
|
|
|
'td[3]//a[@href="http://www.arsenal.com/hospitality/events"]'); |
|
86
|
|
|
|
|
|
|
|
|
87
|
9
|
|
|
|
|
17645
|
$match{is_soldout} = $row->exists('td[6]//span[@class="soldout"]'); |
|
88
|
9
|
|
|
|
|
12946
|
$match{can_exchange} = 0; |
|
89
|
|
|
|
|
|
|
|
|
90
|
9
|
100
|
|
|
|
35
|
if ( !$match{is_soldout} ) { |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
AVAILABILITY: |
|
93
|
7
|
|
|
|
|
34
|
for ( my $j = $i ; $j < $i + 3 ; $j++ ) { |
|
94
|
16
|
|
|
|
|
145
|
my $availability_row = $rows->[$j]; |
|
95
|
|
|
|
|
|
|
|
|
96
|
16
|
|
|
|
|
17
|
my @membership_nodes; |
|
97
|
16
|
100
|
|
|
|
45
|
if ( $j == $i ) { |
|
98
|
7
|
|
|
|
|
31
|
@membership_nodes = |
|
99
|
|
|
|
|
|
|
$availability_row->findnodes('td[5]/img[@title]'); |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
else { |
|
102
|
9
|
|
|
|
|
67
|
@membership_nodes = |
|
103
|
|
|
|
|
|
|
$availability_row->findnodes('td[1]/img[@title]'); |
|
104
|
|
|
|
|
|
|
} |
|
105
|
|
|
|
|
|
|
|
|
106
|
16
|
100
|
|
|
|
28372
|
last AVAILABILITY unless @membership_nodes; |
|
107
|
|
|
|
|
|
|
|
|
108
|
10
|
|
|
|
|
17
|
my ( $availability_forsale, $availability_date ); |
|
109
|
10
|
100
|
|
|
|
30
|
if ( $j == $i ) { |
|
110
|
7
|
|
|
|
|
30
|
( $availability_forsale, $availability_date ) = |
|
111
|
|
|
|
|
|
|
_parse_availability( |
|
112
|
|
|
|
|
|
|
$availability_row->findvalue('td[6]/p') ); |
|
113
|
|
|
|
|
|
|
} |
|
114
|
|
|
|
|
|
|
else { |
|
115
|
3
|
|
|
|
|
12
|
( $availability_forsale, $availability_date ) = |
|
116
|
|
|
|
|
|
|
_parse_availability( |
|
117
|
|
|
|
|
|
|
$availability_row->findvalue('td[2]/p') ); |
|
118
|
|
|
|
|
|
|
} |
|
119
|
|
|
|
|
|
|
|
|
120
|
10
|
|
|
|
|
31
|
my @memberships_for_availability; |
|
121
|
10
|
|
|
|
|
28
|
for my $membership_node (@membership_nodes) { |
|
122
|
11
|
|
|
|
|
46
|
my $membership = $membership_node->attr('title'); |
|
123
|
11
|
|
|
|
|
144
|
given ($membership) { |
|
124
|
11
|
|
|
|
|
33
|
when (/Exchange/) { |
|
125
|
1
|
|
|
|
|
4
|
$match{can_exchange} = 1; |
|
126
|
1
|
|
|
|
|
5
|
last AVAILABILITY; |
|
127
|
|
|
|
|
|
|
} |
|
128
|
10
|
|
|
|
|
45
|
when (/General Sale/) { |
|
129
|
2
|
|
|
|
|
41
|
push( @memberships_for_availability, |
|
130
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
131
|
|
|
|
|
|
|
->GENERAL_SALE ); |
|
132
|
|
|
|
|
|
|
} |
|
133
|
8
|
|
|
|
|
29
|
when (/Red Members/) { |
|
134
|
3
|
|
|
|
|
21
|
push( @memberships_for_availability, |
|
135
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
136
|
|
|
|
|
|
|
->RED ); |
|
137
|
|
|
|
|
|
|
} |
|
138
|
5
|
|
|
|
|
25
|
when (/Silver Members/) { |
|
139
|
3
|
|
|
|
|
21
|
push( @memberships_for_availability, |
|
140
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
141
|
|
|
|
|
|
|
->SILVER ); |
|
142
|
|
|
|
|
|
|
} |
|
143
|
2
|
|
|
|
|
11
|
when (/Platinum \/ Gold Members/) { |
|
144
|
1
|
|
|
|
|
9
|
push( @memberships_for_availability, |
|
145
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
146
|
|
|
|
|
|
|
->PLATINUM_GOLD ); |
|
147
|
|
|
|
|
|
|
} |
|
148
|
1
|
|
|
|
|
6
|
when (/Travel Club/) { |
|
149
|
1
|
|
|
|
|
6
|
push( @memberships_for_availability, |
|
150
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
151
|
|
|
|
|
|
|
->TRAVEL_CLUB ); |
|
152
|
|
|
|
|
|
|
} |
|
153
|
|
|
|
|
|
|
} # given ($membership) |
|
154
|
10
|
|
100
|
|
|
73
|
$match{availability} //= []; |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
} |
|
157
|
9
|
100
|
|
|
|
36
|
if ($availability_forsale) { |
|
|
|
50
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
|
|
159
|
3
|
|
|
|
|
5
|
push @{ $match{availability} }, |
|
|
3
|
|
|
|
|
38
|
|
|
160
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
161
|
|
|
|
|
|
|
->new( |
|
162
|
|
|
|
|
|
|
memberships => \@memberships_for_availability, |
|
163
|
|
|
|
|
|
|
type => |
|
164
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
165
|
|
|
|
|
|
|
->FOR_SALE, |
|
166
|
|
|
|
|
|
|
); |
|
167
|
|
|
|
|
|
|
} |
|
168
|
|
|
|
|
|
|
elsif ($availability_date) { |
|
169
|
6
|
|
|
|
|
11
|
push @{ $match{availability} }, |
|
|
6
|
|
|
|
|
47
|
|
|
170
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
171
|
|
|
|
|
|
|
->new( |
|
172
|
|
|
|
|
|
|
memberships => \@memberships_for_availability, |
|
173
|
|
|
|
|
|
|
type => |
|
174
|
|
|
|
|
|
|
WWW::ArsenalFC::TicketInformation::Match::Availability |
|
175
|
|
|
|
|
|
|
->SCHEDULED, |
|
176
|
|
|
|
|
|
|
date => $availability_date |
|
177
|
|
|
|
|
|
|
); |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
} # for my $membership_node (@membership_nodes) |
|
180
|
|
|
|
|
|
|
} |
|
181
|
|
|
|
|
|
|
|
|
182
|
9
|
|
|
|
|
80
|
my $match = WWW::ArsenalFC::TicketInformation::Match->new(%match); |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# add the category if we can |
|
185
|
9
|
|
|
|
|
146
|
my $category_key = sprintf( "%s:%s", $match->opposition, $match->date ); |
|
186
|
9
|
100
|
|
|
|
70
|
if ( my $category = $self->{categories_hash}->{$category_key} ) { |
|
187
|
3
|
|
|
|
|
12
|
$match->{category} = $category; |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
|
|
190
|
9
|
|
|
|
|
58
|
push @matches, $match; |
|
191
|
|
|
|
|
|
|
} |
|
192
|
|
|
|
|
|
|
|
|
193
|
2
|
|
|
|
|
41
|
$self->{matches} = \@matches; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# populates an HTML::TreeBuilder::XPath tree, unless we already have one |
|
197
|
|
|
|
|
|
|
sub _get_tree { |
|
198
|
4
|
|
|
4
|
|
11
|
my ($self) = @_; |
|
199
|
|
|
|
|
|
|
|
|
200
|
4
|
100
|
|
|
|
34
|
if ( !$self->{tree} ) { |
|
201
|
1
|
|
|
|
|
11
|
$self->{tree} = |
|
202
|
|
|
|
|
|
|
HTML::TreeBuilder::XPath->new_from_content( LWP::Simple::get(URL) ); |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
|
|
205
|
4
|
|
|
|
|
969805
|
return $self->{tree}; |
|
206
|
|
|
|
|
|
|
} |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub _parse_availability { |
|
209
|
10
|
|
|
10
|
|
9000
|
my ($availability) = @_; |
|
210
|
|
|
|
|
|
|
|
|
211
|
10
|
|
|
|
|
23
|
given ($availability) { |
|
212
|
10
|
|
|
|
|
36
|
when (/Buy Now/) { |
|
213
|
3
|
|
|
|
|
10
|
return 1; |
|
214
|
|
|
|
|
|
|
} |
|
215
|
7
|
|
|
|
|
36
|
when (/(\d\d-\d\d-\d\d\d\d)/) { |
|
216
|
6
|
|
|
|
|
28
|
return ( undef, $1 ); |
|
217
|
|
|
|
|
|
|
} |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
} |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
# trims whitespace from a string |
|
222
|
|
|
|
|
|
|
sub _trimWhitespace { |
|
223
|
27
|
|
|
27
|
|
25863
|
my $string = shift; |
|
224
|
27
|
|
|
|
|
100
|
$string =~ s/^\s+//; |
|
225
|
27
|
|
|
|
|
128
|
$string =~ s/\s+$//; |
|
226
|
27
|
|
|
|
|
112
|
return $string; |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
1; |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
__END__ |