File Coverage

blib/lib/Text/Microformat.pm
Criterion Covered Total %
statement 86 93 92.4
branch 8 20 40.0
condition 8 18 44.4
subroutine 21 22 95.4
pod 10 14 71.4
total 133 167 79.6


line stmt bran cond sub pod time code
1             package Text::Microformat;
2 4     4   121191 use strict;
  4         9  
  4         152  
3 4     4   22 use warnings;
  4         7  
  4         313  
4              
5             =head1 NAME
6              
7             Text::Microformat - A Microformat parser
8              
9             =head1 VERSION
10              
11             Version 0.02
12              
13             =cut
14              
15             our $VERSION = '0.04';
16              
17             =head1 SYNOPSIS
18              
19             use Text::Microformat;
20             use LWP::Simple;
21              
22             # Parse a document
23             my $doc = Text::Microformat->new(
24             get('http://phil.windley.org/hcard.html')
25             );
26            
27             # Extract all known Microformats
28             my @formats = $doc->find;
29            
30             my $hcard = shift @formats;
31              
32             # Easiest way to get a value (returns the first one found, else undef)
33              
34             my $full_name = $hcard->Get('fn');
35             my $family_name = $hcard->Get('n.family-name');
36             my $city = $hcard->Get('adr.locality');
37              
38             # Get the human-readable version specifically
39              
40             my $family_name = $hcard->GetH('n.family-name');
41              
42             # Get the machine-readable version specifically
43              
44             my $family_name = $hcard->GetM('n.family-name');
45              
46             # The more powerful interface (access multiple properties)
47            
48             my $family_name = $hcard->n->[0]->family_name->[0]->Value;
49              
50             # Dump to a hash
51            
52             my $hash = $hcard->AsHash;
53            
54             # Dump to YAML
55            
56             print $hcard->ToYAML, "\n";
57            
58             # Free the document and all the formats
59            
60             $doc->delete;
61              
62             =head1 DESCRIPTION
63              
64             Text::Microformat is a Microformat parser for Perl.
65              
66             Text::Microformat sports a very pluggable API, which allows not only new kinds
67             of Microformats to be added, but also extension of the parser itself, to allow
68             new parsing metaphors and source document encodings.
69              
70             =head2 FEATURES
71              
72             =over 4
73              
74             =item * Extracting Microformats from HTML, XHTML and XML
75              
76             =item * Extracting Microformats from entity-encoded or CDATA sections in RSS feeds.
77              
78             =item * The include pattern
79              
80             =item * Microformats built from other Microformats
81              
82             =back
83              
84             =head2 SUPPORTED MICROFORMATS
85              
86             =over 4
87              
88             =item * hCard
89              
90             =back
91              
92             =head2 OTHER SUPPORTED SEMANTIC MARKUP
93              
94             =over 4
95              
96             =item * hGrant
97              
98             =back
99              
100             =cut
101              
102 4     4   3772 use Module::Pluggable require => 1, sub_name => 'plugins';
  4         47251  
  4         32  
103 4     4   577 use Module::Pluggable search_path => 'Text::Microformat::Element', require => 1, sub_name => 'known_formats', except => qr/^Text\::Microformat\::Element\::\w+\::/;
  4         8  
  4         31  
104 4     4   4959 use NEXT;
  4         31871  
  4         139  
105 4     4   43 use base 'Class::Accessor';
  4         10  
  4         4334  
106             __PACKAGE__->mk_accessors(qw/tree content opts formats criteria/);
107 4     4   9103 use Carp;
  4         10  
  4         4569  
108              
109             our @ISA;
110             push @ISA, $_ for __PACKAGE__->plugins;
111              
112             =head1 METHODS
113              
114             =over 4
115              
116             =item * new($content, %opts)
117              
118             Parses the string $content and creates a new Text::Microformat object.
119              
120             Recognized options:
121              
122             =over 4
123              
124             =item * content_type => 'text/html'
125              
126             Specify the content type. Any content type containing 'html' invokes the HTML
127             Parser, and content type containing XML invokes XML Parser. Defaults to
128             'text/html'. (See L and L)
129              
130             =back
131              
132             =cut
133              
134             sub new {
135 20     20 1 11585 my $class = shift;
136 20         48 my $content = shift;
137 20         67 my %opts = @_;
138 20         157 my $c = bless {opts => \%opts, content => $content, formats => []}, $class;
139 20         168 $c->defaults;
140 20         118825 $c->pre_parse;
141 20         4079 $c->parse;
142 20 50       732 croak("Could not find a parser for content type '", $c->opts->{content_type}, "'") unless $c->tree;
143 20         223 $c->post_parse;
144 20         2800 return $c;
145             }
146              
147             =item * find()
148              
149             Returns an array of all known Microformats in the document.
150              
151             =cut
152              
153             sub find {
154 20     20 1 1323 my $c = shift;
155 20   50     132 my $criteria = shift || {};
156 20         85 $c->criteria($criteria);
157 20         296 $c->pre_find_formats;
158 20         28545 $c->find_formats;
159 20         5614 $c->post_find_formats;
160 20         4299 return @{$c->formats};
  20         67  
161             }
162              
163             sub plugin_opts {
164 40     40 0 75 my $c = shift;
165 40         107 my ($package) = caller;
166 40         147 $package =~ s/^Text\::Microformat\::Plugin\:://;
167 40         121 return $c->opts->{$package};
168             }
169              
170             sub defaults {
171 20     20 1 38 my $c = shift;
172 20   50     94 $c->opts->{content_type} ||= 'text/html';
173 20   50     507 $c->opts->{$_} ||= {} for map({s/^Text\::Microformat\::Plugin\:://; $_} __PACKAGE__->plugins);
  60         130638  
  60         232  
174 20 50       1003 $c->opts->{'Parser::HTML'}{empty_element_tags} = 1
175             unless defined $c->opts->{'Parser::HTML'}{empty_element_tags};
176 20         660 $c->NEXT::defaults(@_);
177             }
178              
179             sub parse {
180 20     20 1 38 my $c = shift;
181 20         126 $c->NEXT::parse(@_);
182             }
183              
184             sub pre_parse {
185 20     20 1 41 my $c = shift;
186 20         137 $c->NEXT::pre_parse(@_);
187             }
188              
189             sub post_parse {
190 20     20 1 35 my $c = shift;
191 20         127 $c->NEXT::post_parse(@_);
192             }
193              
194             sub find_formats {
195 20     20 1 117 my $c = shift;
196 20         27 my $format_re;
197            
198 20   33     69 my $formats = $c->criteria->{formats} || $c->criteria->{format};
199 20 50       471 if (defined $formats) {
200 0 0 0     0 if (ref $formats eq 'Regex') {
    0          
201 0         0 $format_re = $formats;
202             }
203             elsif (ref $formats eq 'ARRAY' or !ref $formats) {
204 0 0       0 $format_re = join '|', (ref $formats eq 'ARRAY' ? @$formats : $formats);
205 0         0 $format_re = qr/^(?:$format_re)$/mis;
206             }
207 0         0 print "$format_re\n";
208             }
209            
210 20         105 foreach my $format ($c->known_formats) {
211 100         890502 (my $short_name = $format) =~ s/Text\::Microformat\::Element\:://;
212 100 50 33     384 next if defined $format_re and $short_name !~ $format_re;
213 100 100       898 next unless $format->_params->{criteria};
214 80         694 push @{$c->formats}, $format->Find($c->tree);
  80         336  
215             }
216            
217 20         2854 $c->NEXT::find_formats(@_);
218             }
219              
220             sub pre_find_formats {
221 20     20 1 33 my $c = shift;
222 20         158 $c->NEXT::pre_find_formats(@_);
223             }
224              
225             sub post_find_formats {
226 20     20 1 45 my $c = shift;
227 20         121 $c->NEXT::post_find_formats(@_);
228             }
229              
230             sub class_regex {
231 542     542 0 750 my $c = shift;
232 542         945 my $classes = join '|', @_;
233 542         18655 return qr/(?:\A|\s)(?:$classes)(?:\s|\z)/mis;
234             }
235              
236             # make a regex that matches one or more tagnames (for look_down)
237             # right now it makes a regex that ignores namespaces,
238             # and just matches the local name of the tag.
239             # might want to make this behavior alterable via an option later.
240             sub tag_regex {
241 20     20 0 296 my $c = shift;
242 20         76 my $names = join '|', @_;
243 20         611 return qr/^(?:\w[\w\.-]*:)?(?:$names)$/mis;
244             }
245              
246             # Returns a closure that is a default filter for the
247             # Find() method (I.e. it is passed to $context_element->look_down).
248             # Currently it just ensures that the returned elements:
249             # - are not $context_element
250             # - are not child elements of elements which matched previously
251             #
252             # XXX TODO - Will using a closure here cause memory leaks?
253              
254             sub element_filter {
255 662     662 0 5589 my $c = shift;
256 662         995 my $context_element = shift;
257 662         808 my @found;
258             return sub {
259 166     166   15170 my $e = $_[0];
260 166 100 100     1119 if ($e eq $context_element or grep $e->is_inside($_), @found) {
261 4         58 return 0;
262             }
263 162         1096 push @found, $e;
264 162         451 return 1;
265 662         4316 };
266             }
267              
268             =item * delete()
269              
270             Deletes the underlying parse tree - which is required by L to free up memory. Behavior of Text::Microformat::Element::* objects is undefined after this method is called.
271              
272             =cut
273              
274             sub delete {
275 0     0 1   my $c = shift;
276 0 0         $c->tree->delete if $c->tree;
277             }
278              
279             =back
280              
281             =head1 EXTENDING Text::Microformat
282              
283             =head2 CREATING A NEW FORMAT
284              
285             This is as easy as creating a new module in the Text::Microformat::Element::*
286             namespace, having Text::Microformat::Element as a super-class. It will be
287             auto-loaded by Text::Microformat.
288              
289             Every Microformat element has it's own namespace auto-generated, for example:
290              
291             Text::Microformat::Element::hCard::n::family_name
292              
293             So it's easy to override the default behavior of Text::Microformat::Element
294             via inheritance.
295              
296             See existing formats for hints.
297              
298             =head2 CREATING A PLUGIN
299              
300             This is as easy as creating a new module in the Text::Microformat::Plugin::*
301             namespace. It will be auto-loaded by Text::Microformat. Text::Microformat has
302             several processing phases, and uses L to traverse the plugin chain.
303              
304             Current processing phases are, in order of execution:
305              
306             =over 4
307              
308             =item * defaults
309              
310             Set default options in $c->opts
311              
312             =item * pre_parse
313              
314             Pre-parsing activities (Operations on the document source, perhaps)
315              
316             =item * parse
317              
318             Parsing - at least one plugin must parse $c->content into $c->tree
319              
320             =item * post_parse
321              
322             Post-parsing activities (E.g. the include pattern happens here)
323              
324             =item * pre_find_formats
325              
326             Before looking for Microformats
327              
328             =item * find_formats
329              
330             Populate the $c->formats array with Text::Microformat::Element objects
331              
332             =item * post_find_formats
333              
334             After looking for Microformats
335              
336             =back
337              
338             A plugin may add handlers to one or more phases.
339              
340             See existing plugins for hints.
341              
342             =head1 TODO
343              
344             =over 4
345              
346             =item * Documentation!
347              
348             =item * Add more formats
349              
350             =item * Add filtering options to the find() method
351              
352             =item * Parsing and format-finding performance could definitely be improved
353              
354             =back
355              
356             =head1 SEE ALSO
357              
358             L, L, L
359              
360             =head1 AUTHOR
361              
362             Keith Grennan, C<< >>
363              
364             =head1 BUGS
365              
366             Log bugs and feature requests here: L
367              
368             =head1 SUPPORT
369              
370             Project homepage: L
371              
372             =head1 COPYRIGHT & LICENSE
373              
374             Copyright 2007 Keith Grennan, all rights reserved.
375              
376             This program is free software; you can redistribute it and/or modify it
377             under the same terms as Perl itself.
378              
379             =cut
380              
381             1; # End of Text::Microformat