File Coverage

blib/lib/Web/Sitemap.pm
Criterion Covered Total %
statement 115 121 95.0
branch 25 42 59.5
condition 10 18 55.5
subroutine 23 23 100.0
pod 3 3 100.0
total 176 207 85.0


line stmt bran cond sub pod time code
1             package Web::Sitemap;
2              
3             our $VERSION = '0.902_3';
4              
5 4     4   291083 use strict;
  4         37  
  4         121  
6 4     4   21 use warnings;
  4         7  
  4         114  
7 4     4   2495 use bytes;
  4         58  
  4         21  
8              
9 4     4   3192 use File::Temp;
  4         88692  
  4         287  
10 4     4   1968 use File::Copy;
  4         9729  
  4         243  
11 4     4   2383 use IO::Compress::Gzip qw/gzip $GzipError/;
  4         142053  
  4         485  
12 4     4   2431 use Encode;
  4         39785  
  4         280  
13 4     4   31 use Carp;
  4         8  
  4         190  
14              
15 4     4   1919 use Web::Sitemap::Url;
  4         11  
  4         311  
16              
17             use constant {
18 4         6660 URL_LIMIT => 50000,
19             FILE_SIZE_LIMIT => 50 * 1024 * 1024,
20             FILE_SIZE_LIMIT_MIN => 1024 * 1024,
21              
22             DEFAULT_FILE_PREFIX => 'sitemap.',
23             DEFAULT_TAG => 'pages',
24             DEFAULT_INDEX_NAME => 'sitemap',
25              
26             XML_HEAD => '',
27             XML_MAIN_NAMESPACE => 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"',
28             XML_MOBILE_NAMESPACE => 'xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0"',
29             XML_IMAGES_NAMESPACE => 'xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"'
30              
31 4     4   31 };
  4         9  
32              
33             sub new
34             {
35 5     5 1 268236 my ($class, %p) = @_;
36              
37 5         18 my %allowed_keys = map { $_ => 1 } qw(
  70         141  
38             output_dir temp_dir loc_prefix
39             url_limit file_size_limit file_prefix
40             file_loc_prefix default_tag index_name
41             mobile images namespace
42             charset move_from_temp_action
43             );
44              
45 5         22 my @bad_keys = grep { !exists $allowed_keys{$_} } keys %p;
  11         34  
46 5 100       125 croak "Unknown parameters: @bad_keys" if @bad_keys;
47              
48 4         40 my $self = {
49             loc_prefix => '',
50             tags => {},
51              
52             url_limit => URL_LIMIT,
53             file_size_limit => FILE_SIZE_LIMIT,
54             file_prefix => DEFAULT_FILE_PREFIX,
55             file_loc_prefix => '',
56             default_tag => DEFAULT_TAG,
57             index_name => DEFAULT_INDEX_NAME,
58             mobile => 0,
59             images => 0,
60             charset => 'utf8',
61              
62             %p, # actual input values
63             };
64              
65 4   33     37 $self->{file_loc_prefix} ||= $self->{loc_prefix};
66              
67 4 100       13 if ($self->{file_size_limit} < FILE_SIZE_LIMIT_MIN) {
68 1         2 $self->{file_size_limit} = FILE_SIZE_LIMIT_MIN;
69             }
70              
71 4 50       15 if ($self->{namespace}) {
72              
73             $self->{namespace} = [$self->{namespace}]
74 0 0       0 if !ref $self->{namespace};
75              
76             croak 'namespace must be scalar or array ref!'
77 0 0       0 if ref $self->{namespace} ne 'ARRAY';
78             }
79              
80 4 100       14 unless ($self->{output_dir}) {
81 1         194 croak 'output_dir expected!';
82             }
83              
84 3 50 33     19 if ($self->{temp_dir} and not -w $self->{temp_dir}) {
85 0         0 croak sprintf "Can't write to temp_dir '%s' (error: %s)", $self->{temp_dir}, $!;
86             }
87              
88 3 100 66     27 if ($self->{move_from_temp_action} and ref $self->{move_from_temp_action} ne 'CODE') {
89 1         99 croak 'move_from_temp_action must be code ref!';
90             }
91              
92 2         15 return bless $self, $class;
93             }
94              
95             sub add
96             {
97 3     3 1 100121 my ($self, $url_list, %p) = @_;
98              
99 3   66     30 my $tag = $p{tag} || $self->{default_tag};
100              
101 3 50       13 if (ref $url_list ne 'ARRAY') {
102 0         0 croak 'The list of sitemap URLs must be array ref';
103             }
104              
105 3         11 for my $url (@$url_list) {
106             my $data = Web::Sitemap::Url->new(
107             $url,
108             mobile => $self->{mobile},
109             loc_prefix => $self->{loc_prefix},
110 50044         131633 )->to_xml_string;
111              
112 50044 100       136068 if ($self->_file_limit_near($tag, bytes::length $data)) {
113 6         16 $self->_next_file($tag);
114             }
115              
116 50044         113210 $self->_append_url($tag, $data);
117             }
118             }
119              
120             sub finish
121             {
122 2     2 1 1367 my ($self, %p) = @_;
123              
124 2 50       7 return unless keys %{$self->{tags}};
  2         17  
125              
126 2         18 my $index_temp_file_name = $self->_temp_file->filename;
127 2 50       871 open my $index_file, '>' . $index_temp_file_name or croak "Can't open file '$index_temp_file_name'! $!\n";
128              
129 2         565 print {$index_file} XML_HEAD;
  2         20  
130 2         5 printf {$index_file} "\n", XML_MAIN_NAMESPACE;
  2         13  
131              
132 2         4 for my $tag (sort keys %{$self->{tags}}) {
  2         15  
133 3         9 my $data = $self->{tags}{$tag};
134              
135 3         12 $self->_close_file($tag);
136 3         9675 for my $page (1 .. $data->{page}) {
137 9         26 printf {$index_file} "\n%s/%s", $self->{file_loc_prefix},
138 9         17 $self->_file_name($tag, $page);
139             }
140             }
141              
142 2         6 print {$index_file} "\n";
  2         5  
143 2         80 close $index_file;
144              
145             $self->_move_from_temp(
146             $index_temp_file_name,
147 2         19 $self->{output_dir} . '/' . $self->{index_name} . '.xml'
148             );
149             }
150              
151             sub _move_from_temp
152             {
153 11     11   31 my ($self, $temp_file_name, $public_file_name) = @_;
154              
155             #printf "move %s -> %s\n", $temp_file_name, $public_file_name;
156              
157 11 50       31 if ($self->{move_from_temp_action}) {
158 11         37 $self->{move_from_temp_action}($temp_file_name, $public_file_name);
159             }
160             else {
161 0 0       0 File::Copy::move($temp_file_name, $public_file_name)
162             or croak sprintf 'move %s -> %s error: %s', $temp_file_name, $public_file_name, $!;
163             }
164             }
165              
166             sub _file_limit_near
167             {
168 50044     50044   170009 my ($self, $tag, $new_portion_size) = @_;
169              
170 50044 100       106826 return 0 unless defined $self->{tags}{$tag};
171              
172             # printf("tag: %s.%d; url: %d; gzip_size: %d (%d)\n",
173             # $tag,
174             # $self->{tags}->{$tag}->{page},
175             # $self->{tags}->{$tag}->{url_count},
176             # $self->{tags}->{$tag}->{file_size},
177             # $self->{file_size_limit}
178             # );
179              
180             return (
181             $self->{tags}{$tag}{url_count} >= $self->{url_limit}
182             ||
183              
184             # 200 bytes should be well enough for the closing tags at the end of the file
185 50041   66     234866 ($self->{tags}{$tag}{file_size} + $new_portion_size) >= ($self->{file_size_limit} - 200)
186             );
187             }
188              
189             sub _temp_file
190             {
191 11     11   26 my ($self) = @_;
192              
193             return File::Temp->new(
194             UNLINK => 1,
195 11 50       93 $self->{temp_dir} ? (DIR => $self->{temp_dir}) : ()
196             );
197             }
198              
199             sub _set_new_file
200             {
201 9     9   26 my ($self, $tag) = @_;
202              
203 9         25 my $temp_file = $self->_temp_file;
204              
205 9         4681 $self->{tags}{$tag}{page}++;
206 9         24 $self->{tags}{$tag}{url_count} = 0;
207 9         19 $self->{tags}{$tag}{file_size} = 0;
208 9 50       32 $self->{tags}{$tag}{file} = IO::Compress::Gzip->new($temp_file->filename)
209             or croak "gzip failed: $GzipError\n";
210 9         15108 $self->{tags}{$tag}{file}->autoflush;
211 9         796 $self->{tags}{$tag}{temp_file} = $temp_file;
212              
213             # Do not check the file for oversize because it is empty and will likely
214             # not exceed 1MB with initial tags alone
215              
216 9         1735 my @namespaces = (XML_MAIN_NAMESPACE);
217             push @namespaces, XML_MOBILE_NAMESPACE
218 9 50       34 if $self->{mobile};
219             push @namespaces, XML_IMAGES_NAMESPACE
220 9 50       34 if $self->{images};
221 0         0 push @namespaces, @{$self->{namespace}}
222 9 50       26 if $self->{namespace};
223              
224 9         72 $self->_append(
225             $tag,
226             sprintf("%s\n", XML_HEAD, join(' ', @namespaces))
227             );
228             }
229              
230             sub _file_handle
231             {
232 50071     50071   71259 my ($self, $tag) = @_;
233              
234 50071 100       95161 unless (exists $self->{tags}{$tag}) {
235 3         11 $self->_set_new_file($tag);
236             }
237              
238 50071         117468 return $self->{tags}{$tag}{file};
239             }
240              
241             sub _append
242             {
243 50062     50062   78834 my ($self, $tag, $data) = @_;
244              
245 50062         92614 $self->_file_handle($tag)->print(Encode::encode($self->{charset}, $data));
246 50062         4755472 $self->{tags}{$tag}{file_size} += bytes::length $data;
247             }
248              
249             sub _append_url
250             {
251 50044     50044   84698 my ($self, $tag, $data) = @_;
252              
253 50044         103499 $self->_append($tag, $data);
254 50044         185220 $self->{tags}{$tag}{url_count}++;
255             }
256              
257             sub _next_file
258             {
259 6     6   17 my ($self, $tag) = @_;
260              
261 6         20 $self->_close_file($tag);
262 6         69607 $self->_set_new_file($tag);
263             }
264              
265             sub _close_file
266             {
267 9     9   22 my ($self, $tag) = @_;
268              
269 9         24 $self->_append($tag, "\n");
270 9         39 $self->_file_handle($tag)->close;
271              
272             $self->_move_from_temp(
273             $self->{tags}{$tag}{temp_file}->filename,
274 9         3977 $self->{output_dir} . '/' . $self->_file_name($tag)
275             );
276             }
277              
278             sub _file_name
279             {
280 18     18   125 my ($self, $tag, $page) = @_;
281             return
282             $self->{file_prefix}
283             . $tag
284             . '.'
285             . ($page || $self->{tags}{$tag}{page})
286 18   66     142 . '.xml.gz'
287             ;
288             }
289              
290             1;
291              
292             __END__