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_2';
4              
5 4     4   288455 use strict;
  4         37  
  4         121  
6 4     4   22 use warnings;
  4         8  
  4         112  
7 4     4   2583 use bytes;
  4         60  
  4         19  
8              
9 4     4   3106 use File::Temp;
  4         88298  
  4         298  
10 4     4   2092 use File::Copy;
  4         9594  
  4         243  
11 4     4   2423 use IO::Compress::Gzip qw/gzip $GzipError/;
  4         140231  
  4         489  
12 4     4   2472 use Encode;
  4         40022  
  4         294  
13 4     4   33 use Carp;
  4         10  
  4         205  
14              
15 4     4   2036 use Web::Sitemap::Url;
  4         10  
  4         245  
16              
17             use constant {
18 4         6710 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   29 };
  4         9  
32              
33              
34             sub new {
35 5     5 1 268215 my ($class, %p) = @_;
36              
37 5         20 my %allowed_keys = map { $_ => 1 } qw(
  70         137  
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         23 my @bad_keys = grep { !exists $allowed_keys{$_} } keys %p;
  11         31  
46 5 100       124 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     35 $self->{file_loc_prefix} ||= $self->{loc_prefix};
66              
67 4 100       16 if ($self->{file_size_limit} < FILE_SIZE_LIMIT_MIN) {
68 1         3 $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       12 unless ($self->{output_dir}) {
81 1         204 croak 'output_dir expected!';
82             }
83              
84 3 50 33     21 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     23 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         16 return bless $self, $class;
93             }
94              
95             sub add {
96 3     3 1 99704 my ($self, $url_list, %p) = @_;
97              
98 3   66     36 my $tag = $p{tag} || $self->{default_tag};
99              
100 3 50       19 if (ref $url_list ne 'ARRAY') {
101 0         0 croak 'The list of sitemap URLs must be array ref';
102             }
103              
104 3         16 for my $url (@$url_list) {
105             my $data = Web::Sitemap::Url->new(
106             $url,
107             mobile => $self->{mobile},
108             loc_prefix => $self->{loc_prefix},
109 50044         135339 )->to_xml_string;
110              
111 50044 100       139150 if ($self->_file_limit_near($tag, bytes::length $data)) {
112 6         22 $self->_next_file($tag);
113             }
114              
115 50044         106649 $self->_append_url($tag, $data);
116             }
117             }
118              
119             sub finish {
120 2     2 1 1347 my ($self, %p) = @_;
121              
122 2 50       6 return unless keys %{$self->{tags}};
  2         17  
123              
124 2         22 my $index_temp_file_name = $self->_temp_file->filename;
125 2 50       802 open my $index_file, '>' . $index_temp_file_name or croak "Can't open file '$index_temp_file_name'! $!\n";
126              
127 2         557 print {$index_file} XML_HEAD;
  2         31  
128 2         6 printf {$index_file} "\n", XML_MAIN_NAMESPACE;
  2         13  
129              
130 2         6 for my $tag (sort keys %{$self->{tags}}) {
  2         18  
131 3         10 my $data = $self->{tags}{$tag};
132              
133 3         12 $self->_close_file($tag);
134 3         10416 for my $page (1 .. $data->{page}) {
135 9         16 printf {$index_file} "\n%s/%s", $self->{file_loc_prefix}, $self->_file_name($tag, $page);
  9         23  
136             }
137             }
138              
139 2         8 print {$index_file} "\n";
  2         4  
140 2         78 close $index_file;
141              
142             $self->_move_from_temp(
143             $index_temp_file_name,
144 2         21 $self->{output_dir}. '/'. $self->{index_name}. '.xml'
145             );
146             }
147              
148             sub _move_from_temp {
149 11     11   35 my ($self, $temp_file_name, $public_file_name) = @_;
150              
151             #printf "move %s -> %s\n", $temp_file_name, $public_file_name;
152              
153 11 50       33 if ($self->{move_from_temp_action}) {
154 11         44 $self->{move_from_temp_action}($temp_file_name, $public_file_name);
155             }
156             else {
157 0 0       0 File::Copy::move($temp_file_name, $public_file_name)
158             or croak sprintf 'move %s -> %s error: %s', $temp_file_name, $public_file_name, $!;
159             }
160             }
161              
162             sub _file_limit_near {
163 50044     50044   170847 my ($self, $tag, $new_portion_size) = @_;
164              
165 50044 100       106984 return 0 unless defined $self->{tags}{$tag};
166              
167             # printf("tag: %s.%d; url: %d; gzip_size: %d (%d)\n",
168             # $tag,
169             # $self->{tags}->{$tag}->{page},
170             # $self->{tags}->{$tag}->{url_count},
171             # $self->{tags}->{$tag}->{file_size},
172             # $self->{file_size_limit}
173             # );
174              
175             return (
176             $self->{tags}{$tag}{url_count} >= $self->{url_limit}
177             ||
178             # 200 bytes should be well enough for the closing tags at the end of the file
179 50041   66     236331 ($self->{tags}{$tag}{file_size} + $new_portion_size) >= ($self->{file_size_limit} - 200)
180             );
181             }
182              
183             sub _temp_file {
184 11     11   25 my ($self) = @_;
185              
186             return File::Temp->new(
187             UNLINK => 1,
188 11 50       104 $self->{temp_dir} ? ( DIR => $self->{temp_dir} ) : ()
189             );
190             }
191              
192             sub _set_new_file {
193 9     9   26 my ($self, $tag) = @_;
194              
195 9         27 my $temp_file = $self->_temp_file;
196              
197 9         4841 $self->{tags}{$tag}{page}++;
198 9         26 $self->{tags}{$tag}{url_count} = 0;
199 9         17 $self->{tags}{$tag}{file_size} = 0;
200 9 50       34 $self->{tags}{$tag}{file} = IO::Compress::Gzip->new($temp_file->filename)
201             or croak "gzip failed: $GzipError\n";
202 9         16060 $self->{tags}{$tag}{file}->autoflush;
203 9         809 $self->{tags}{$tag}{temp_file} = $temp_file;
204              
205             # Do not check the file for oversize because it is empty and will likely
206             # not exceed 1MB with initial tags alone
207              
208 9         19257 my @namespaces = (XML_MAIN_NAMESPACE);
209             push @namespaces, XML_MOBILE_NAMESPACE
210 9 50       39 if $self->{mobile};
211             push @namespaces, XML_IMAGES_NAMESPACE
212 9 50       35 if $self->{images};
213 0         0 push @namespaces, @{$self->{namespace}}
214 9 50       51 if $self->{namespace};
215              
216 9         89 $self->_append(
217             $tag,
218             sprintf("%s\n", XML_HEAD, join(' ', @namespaces))
219             );
220             }
221              
222             sub _file_handle {
223 50071     50071   77706 my ($self, $tag) = @_;
224              
225 50071 100       96664 unless (exists $self->{tags}{$tag}) {
226 3         15 $self->_set_new_file($tag);
227             }
228              
229 50071         134376 return $self->{tags}{$tag}{file};
230             }
231              
232             sub _append {
233 50062     50062   74526 my ($self, $tag, $data) = @_;
234              
235 50062         86132 $self->_file_handle($tag)->print(Encode::encode($self->{charset}, $data));
236 50062         4769726 $self->{tags}{$tag}{file_size} += bytes::length $data;
237             }
238              
239             sub _append_url {
240 50044     50044   85497 my ($self, $tag, $data) = @_;
241              
242 50044         104550 $self->_append($tag, $data);
243 50044         196202 $self->{tags}{$tag}{url_count}++;
244             }
245              
246             sub _next_file {
247 6     6   13 my ($self, $tag) = @_;
248              
249 6         25 $self->_close_file($tag);
250 6         60367 $self->_set_new_file($tag);
251             }
252              
253             sub _close_file {
254 9     9   23 my ($self, $tag) = @_;
255              
256 9         53 $self->_append($tag, "\n");
257 9         44 $self->_file_handle($tag)->close;
258              
259             $self->_move_from_temp(
260             $self->{tags}{$tag}{temp_file}->filename,
261 9         3610 $self->{output_dir}. '/'. $self->_file_name($tag)
262             );
263             }
264              
265             sub _file_name {
266 18     18   140 my ($self, $tag, $page) = @_;
267             return
268             $self->{file_prefix}
269             . $tag
270             . '.'
271             . ($page || $self->{tags}{$tag}{page})
272 18   66     151 . '.xml.gz'
273             ;
274             }
275              
276             1;
277              
278             __END__