File Coverage

blib/lib/HTML/ExtractMeta.pm
Criterion Covered Total %
statement 72 72 100.0
branch 5 6 83.3
condition 12 20 60.0
subroutine 15 15 100.0
pod 10 11 90.9
total 114 124 91.9


line stmt bran cond sub pod time code
1             package HTML::ExtractMeta;
2 2     2   39592 use Moose;
  2         574628  
  2         10  
3 2     2   9954 use namespace::autoclean;
  2         10550  
  2         7  
4              
5 2     2   1032 use Mojo::DOM;
  2         95160  
  2         1703  
6              
7             =head1 NAME
8              
9             HTML::ExtractMeta - Helper class for extracting useful meta data from HTML pages.
10              
11             =head1 VERSION
12              
13             Version 0.20
14              
15             =cut
16              
17             our $VERSION = '0.20';
18              
19             =head1 SYNOPSIS
20              
21             use HTML::ExtractMeta;
22              
23             my $em = HTML::ExtractMeta->new( $html );
24              
25             print "Title = " . $em->title . "\n";
26             print "Description = " . $em->description . "\n";
27             print "Author = " . $em->author . "\n";
28             print "URL = " . $em->url . "\n";
29             print "Site name = " . $em->site_name . "\n";
30             print "Type = " . $em->type . "\n";
31             print "Locale = " . $em->locale . "\n";
32             print "Image URL = " . $em->image_url . "\n";
33             print "Authors = " . join( ', ', @{$em->get_authors} ) . "\n";
34             print "Keywords = " . join( ', ', @{$em->get_keywords} ) . "\n";
35              
36             =head1 DESCRIPTION
37              
38             HTML::ExtractMeta is a helper class for extracting useful metadata from HTML
39             pages, like their title, description, authors etc.
40              
41             =head1 METHODS
42              
43             =head2 new( %opts )
44              
45             Returns a new HTML::ExtractMeta instance. Requires HTML as input argument;
46              
47             my $em = HTML::ExtractMeta->new( $html );
48              
49             =cut
50              
51             has 'html' => (
52             isa => 'Str',
53             is => 'rw',
54             required => 1,
55             default => '',
56             );
57              
58             around BUILDARGS => sub {
59             my $orig = shift;
60             my $class = shift;
61              
62             if ( @_ == 1 && !ref $_[0] ) {
63             return $class->$orig( html => $_[0] );
64             }
65             else {
66             return $class->$orig( @_ );
67             }
68             };
69              
70             has '_dom' => (
71             isa => 'Mojo::DOM',
72             is => 'ro',
73             lazy => 1,
74             default => sub { Mojo::DOM->new(shift->html) },
75             );
76              
77             has '_meta' => (
78             isa => 'ArrayRef[HashRef]',
79             is => 'ro',
80             lazy => 1,
81             default => sub {
82             my $self = shift;
83              
84             # my %meta = ();
85             my @meta = ();
86              
87             foreach ( $self->_dom->find('meta')->each ) {
88             my $name = $_->attr( 'name' ) // $_->attr( 'property' ) // $_->attr( 'itemprop' ) // '';
89             my $content = $_->attr( 'content' ) // '';
90              
91             if ( length $name && length $content ) {
92             # $meta{ $name } = squish( $content );
93             push( @meta, { $name => squish($content) } );
94             }
95             }
96              
97             # return \%meta;
98             return \@meta;
99             },
100             );
101              
102             sub _get {
103 80     80   66 my $self = shift;
104 80   50     127 my $what = shift || [];
105              
106 80         68 my @data = ();
107              
108 80         50 foreach my $w ( @{$what} ) {
  80         93  
109 141         102 foreach my $m ( @{$self->_meta} ) {
  141         3184  
110 3268 100       3875 if ( my $d = $m->{$w} ) {
111 67         87 push( @data, $d );
112             }
113             }
114             }
115              
116 80         289 return \@data;
117             }
118              
119             =head2 title
120              
121             Returns the HTML page's title.
122              
123             =cut
124              
125             sub title {
126 6     6 1 20 my $self = shift;
127              
128 6         11 my @ids = (
129             'og:title',
130             'twitter:title',
131             );
132              
133 6   50     12 my $title = $self->_get( \@ids )->[0] // '';
134              
135 6         33 $title =~ s/\s*\|.+//;
136              
137 6         9 $title =~ s/^\w+\.\w+\s+\-\s+//;
138 6         11 $title =~ s/^\w+\.\w+\s+\–\s+//;
139              
140 6         11 $title =~ s/\s+\-\s+[[:upper:]].+//;
141 6         9 $title =~ s/\s+\–\s+[[:upper:]].+//;
142              
143 6         11 return squish( $title );
144             }
145              
146             =head2 description
147              
148             Returns the HTML page's description.
149              
150             =cut
151              
152             sub description {
153 6     6 1 9 my $self = shift;
154              
155 6         19 my @ids = (
156             'og:description',
157             'twitter:description',
158             'description',
159             'Description',
160             );
161              
162 6   50     18 return $self->_get( \@ids )->[0] // '';
163             }
164              
165             =head2 url
166              
167             Returns the HTML page's URL.
168              
169             =cut
170              
171             sub url {
172 6     6 1 8 my $self = shift;
173              
174 6         16 my @ids = (
175             'og:url',
176             'twitter:url',
177             );
178              
179 6   100     13 return $self->_get( \@ids )->[0] // '';
180             }
181              
182             =head2 image_url
183              
184             Returns the HTML page's descriptive image URL.
185              
186             =cut
187              
188             sub image_url {
189 5     5 1 7 my $self = shift;
190              
191 5         14 my @ids = (
192             'og:image',
193             'og:image:url',
194             'og:image:secure_url',
195             'twitter:image',
196             );
197              
198 5   50     12 return $self->_get( \@ids )->[0] // '';
199             }
200              
201             =head2 site_name
202              
203             Returns the HTML page's site name.
204              
205             =cut
206              
207             sub site_name {
208 5     5 1 8 my $self = shift;
209              
210 5         15 my @ids = (
211             'og:site_name',
212             'application-name',
213             'twitter:site',
214             );
215              
216 5   50     9 return $self->_get( \@ids )->[0] // '';
217             }
218              
219             =head2 type
220              
221             Returns the HTML page's type.
222              
223             =cut
224              
225             sub type {
226 6     6 1 9 my $self = shift;
227              
228 6         10 my @ids = (
229             'og:type',
230             );
231              
232 6   50     13 return $self->_get( \@ids )->[0] // '';
233             }
234              
235             =head2 locale
236              
237             Returns the HTML page's locale.
238              
239             =cut
240              
241             sub locale {
242 3     3 1 3 my $self = shift;
243              
244 3         9 my @ids = (
245             'og:locale',
246             'inLanguage',
247             'Content-Language',
248             );
249              
250 3   50     4 return $self->_get( \@ids )->[0] // '';
251             }
252              
253             =head2 authors
254              
255             Returns the HTML page's author names as an array reference.
256              
257             =cut
258              
259             sub authors {
260 8     8 1 8 my $self = shift;
261              
262 8         17 my @ids = (
263             'article:author',
264             'author',
265             'Author',
266             'twitter:creator',
267             'DC.creator',
268             );
269              
270 8         9 my @authors = ();
271              
272 8         12 foreach my $id ( @ids ) {
273 40         36 foreach ( @{$self->_get([$id])} ) {
  40         61  
274 13         20 push( @authors, $_ );
275             }
276             }
277              
278 8         55 return \@authors;
279             }
280              
281             =head2 author
282              
283             Helper method; returns the HTML page's first mentioned author. Basically the
284             same as:
285              
286             my $author = $em->authors->[0];
287              
288             =cut
289              
290             sub author {
291 5     5 1 10 my $self = shift;
292              
293 5   100     13 return $self->authors->[0] // '';
294             }
295              
296             =head2 keywords
297              
298             Returns the HTML page's keywords.
299              
300             =cut
301              
302             sub keywords {
303 3     3 1 4 my $self = shift;
304              
305 3         7 my @ids = (
306             'keywords',
307             );
308              
309 3         4 my @keywords = ();
310 3         6 my %seen = ();
311              
312 3         6 foreach my $id ( @ids ) {
313 3 100       10 if ( my $keywords = $self->_get([$id])->[0] ) {
314 1         28 foreach my $keyword ( split(/\s*,\s*/, $keywords) ) {
315 15 50       22 unless ( $seen{$keyword} ) {
316 15         12 push( @keywords, $keyword );
317 15         22 $seen{ $keyword }++;
318             }
319             }
320             }
321             }
322              
323 3         19 return \@keywords;
324             }
325              
326             sub squish {
327 144   50 144 0 179 my $str = shift // '';
328              
329 144         447 $str =~ s/\s+/ /sg;
330 144         179 $str =~ s/^\s+//;
331 144         255 $str =~ s/\s+$//;
332              
333 144         439 return $str;
334             }
335              
336             __PACKAGE__->meta->make_immutable;
337              
338             1;
339              
340             =head1 AUTHOR
341              
342             Tore Aursand, C<< <toreau at gmail.com> >>
343              
344             =head1 BUGS
345              
346             Please report any bugs or feature requests to the web interface at L<https://rt.cpan.org/Public/Dist/Display.html?Name=HTML-ExtractMeta>.
347              
348             =head1 SUPPORT
349              
350             You can find documentation for this module with the perldoc command.
351              
352             perldoc HTML::ExtractMeta
353              
354             You can also look for information at:
355              
356             =over 4
357              
358             =item * AnnoCPAN: Annotated CPAN documentation
359              
360             L<http://annocpan.org/dist/HTML-ExtractMeta>
361              
362             =item * CPAN Ratings
363              
364             L<http://cpanratings.perl.org/d/HTML-ExtractMeta>
365              
366             =item * Search CPAN
367              
368             L<http://search.cpan.org/dist/HTML-ExtractMeta/>
369              
370             =back
371              
372             =head1 LICENSE AND COPYRIGHT
373              
374             Copyright 2012-2016 Tore Aursand.
375              
376             This program is free software; you can redistribute it and/or modify it
377             under the terms of the the Artistic License (2.0). You may obtain a
378             copy of the full license at:
379              
380             L<http://www.perlfoundation.org/artistic_license_2_0>
381              
382             Any use, modification, and distribution of the Standard or Modified
383             Versions is governed by this Artistic License. By using, modifying or
384             distributing the Package, you accept this license. Do not use, modify,
385             or distribute the Package, if you do not accept this license.
386              
387             If your Modified Version has been derived from a Modified Version made
388             by someone other than you, you are nevertheless required to ensure that
389             your Modified Version complies with the requirements of this license.
390              
391             This license does not grant you the right to use any trademark, service
392             mark, tradename, or logo of the Copyright Holder.
393              
394             This license includes the non-exclusive, worldwide, free-of-charge
395             patent license to make, have made, use, offer to sell, sell, import and
396             otherwise transfer the Package with respect to any patent claims
397             licensable by the Copyright Holder that are necessarily infringed by the
398             Package. If you institute patent litigation (including a cross-claim or
399             counterclaim) against any party alleging that the Package constitutes
400             direct or contributory patent infringement, then this Artistic License
401             to you shall terminate on the date that such litigation is filed.
402              
403             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
404             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
405             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
406             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
407             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
408             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
409             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
410             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
411              
412             =cut