File Coverage

blib/lib/HTML/ExtractMeta.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


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