File Coverage

blib/lib/Text/vFile.pm
Criterion Covered Total %
statement 108 136 79.4
branch 45 86 52.3
condition 6 9 66.6
subroutine 12 13 92.3
pod 8 8 100.0
total 179 252 71.0


line stmt bran cond sub pod time code
1             package Text::vFile;
2              
3 8     8   25310 use strict;
  8         14  
  8         2829  
4              
5             =head1 NAME
6              
7             Text::vFile - Generic module which can read and write "vFile" files such as vCard (RFC 2426) and vCalendar (RFC 2445).
8             The result of loading this data is a collection of objects which will grant you easy access to the properties. Then
9             the module can write your objects back to a data file.
10              
11             =head1 SYNOPIS
12              
13             use Text::vFile;
14              
15             my $objects = Text::vCard->load( "foo.vCard", "blort.vCard", "whee.vCard" );
16              
17             foreach my $card (@$objects) {
18             spam ( $card->email('pref') );
19             }
20              
21             # OR
22            
23             my $reader = Text::vFile->new( source_file => "foo.vCard" );
24             while ( my $object = $reader->next ) {
25             spam ( $object );
26             }
27              
28             # OR
29            
30             my $reader = Text::vFile->new( source_text => $vcard_data );
31             while ( my $vcard = <$reader> ) {
32             spam ( $vcard );
33             }
34              
35              
36             =head1 DETAILS
37              
38             The way this processor works is that it reads the vFile line by line.
39              
40             1 - BEGIN:(.*) tag
41              
42             $1 is looked up in classMap; class is loaded; new object of this class is created
43             ie/ $Text::vFile::classMap{'VCARD'}="Text::vCard";
44             $object=$classMap{'VCARD'}->new;
45              
46             n.b. classMap is a package variable for Text::vFile
47              
48             2 - All lines are read and stored until a BEGIN tag (goto 1) or END tag (goto 3) is reached
49              
50             3 - END:(.*) tag
51              
52             Signals that all entry data has been obtained and now the rows of data are processed
53              
54             4 - Data is concatenated - thanks to Text::iCal for the strategy; the tag label and data are obtained
55              
56             5 - The data handler is identified via $object->varHandler->{$label}
57              
58             There are some generic handlers for common data types such as simple strings, dates, etc. More
59             elaborate data types such as N, ADR, etc. need special treatment and are declared explititly
60             in classes as "load_XXX" such as "load_N"
61              
62             You should be able to override and extend the processing by taking Text::vFile::Base.pm as your example
63             and adjusting as necessary.
64              
65             The resulting data structure is a bit bulky - but is such that it can express vFile data completely and
66             reliably
67              
68             $VAR1 = bless( {
69              
70             'EMAIL' => [
71             {
72             'attr' => {
73             'email' => [
74             'HOME'
75             ],
76             'type' => []
77             },
78             'sequence' => 1,
79             'type' => {
80             'internet' => 1
81             },
82             'value' => 'email\\@domain.com'
83             }
84             ],
85             'TITLE' => {
86             'value' => 'Job Title'
87             },
88             'X-ICQ' => [
89             {
90             'attr' => {
91             'type' => [
92             'WORK',
93             'pref'
94             ]
95             },
96             'sequence' => 11,
97             'type' => {
98             'pref' => 1,
99             'work' => 1
100             },
101             'value' => '12341234'
102             }
103             ],
104             '_lines' => [
105             'VERSION:2.1',
106             'N:Person;Test,Given;;;',
107             'FN:Test Person',
108             ....
109             ]
110             }, "Text::vCard");
111              
112             =head1 METHODS
113              
114             =over 4
115              
116             =item \@objects = load( filename [, filename ... ] )
117              
118             Loads the vFiles found in filenames supplied and returns all found items an array of objects.
119              
120             =cut
121              
122             sub load {
123              
124 3     3 1 12 my $self=shift;
125 3 50       10 $self=$self->new unless ref($self);
126              
127 3         22 my @objects=();
128              
129 3         15 foreach my $fn (@_) {
130              
131 3         9 $self->source_file( $fn );
132 3         25 while ( my $object = $self->next ) {
133 1         3 push @objects, $object;
134             }
135              
136             }
137              
138 1 50       18 return wantarray ? @objects : \@objects;
139              
140             }
141              
142             =item \@objects = parse( string [, string ... ] )
143              
144             Loads the vFiles found in the strings passed in and returns all found items as objects.
145              
146             =cut
147              
148             sub parse {
149              
150 0     2 1 0 my $self=shift;
151 0 0       0 $self=$self->new unless ref($self);
152              
153 0         0 my @objects=();
154              
155 0         0 foreach my $text (@_) {
156              
157 0         0 $self->source_text( $text );
158 0         0 until ( $self->eod ) {
159 0         0 push @objects, $self->next;
160             }
161              
162             }
163              
164 0 0       0 return wantarray ? @objects : \@objects;
165              
166             }
167              
168             sub _open {
169              
170 3     3   7 my $self=shift;
171              
172 3 50       10 warn "No filename supplied" && return unless $self->{'source_file'};
173            
174 3 50       143 open ($self->{'fh'}, $self->{'source_file'}) or warn "Cannot open $self->{'source_file'}\n";
175              
176             }
177              
178             =item $loader->source_file( name )
179              
180             Sets this filename to be the source of vfile data. Only one filename, can contain many vfile entries.
181              
182             =cut
183              
184             sub source_file {
185              
186 3     3 1 6 my $self=shift;
187              
188 3 50       12 if (@_) {
189 3         100 $self->{'source_file'} = shift;
190 3         8 delete $self->{'fh'};
191 3         6 delete $self->{'source_text'};
192             }
193              
194 3         9 return $self->{'source_file'};
195              
196             }
197              
198             =item $loader->source_text( $scalar )
199              
200             Sets this scalar to be the source of vfile data. Can contain many vfile.
201              
202             =cut
203              
204             sub source_text {
205              
206 2     2 1 3 my $self=shift;
207              
208 2 50       9 if (@_) {
209 2         77 $self->{'source_text'} = shift;
210 2         6 delete $self->{'fh'};
211 2         6 delete $self->{'source_file'};
212             }
213              
214 2         9 return $self->{'source_text'};
215              
216             }
217              
218             # Classes inject their desired mappings
219 8     8   72 use vars qw(%classMap);
  8         20  
  8         1209  
220             %classMap=(
221              
222             VFILE => "Text::vFile::Base",
223              
224             # VCALENDAR => "Text::vCalendar",
225             # VALARM => "Text::vCalendar::vAlarm",
226             # VEVENT => "Text::vCalendar::vEvent",
227             # VTODO => "Text::vCalendar::vTodo",
228              
229             );
230              
231             =item $object = class->new( options )
232              
233             Create a new vfile loader. You will need to set its source to either a source_file or source_text.
234             Then use the next method to get each next object.
235              
236             =cut
237              
238             sub new {
239              
240 5 50   5 1 23 my $class = ref($_[0]) ? ref(shift) : shift;
241 5 50       26 my $opts = ref($_[0]) ? $_[0] : {@_};
242              
243 5         15 my $self = bless {}, $class;
244              
245 5         20 map { $self->$_( $opts->{$_} ) } keys %$opts;
  4         24  
246              
247 5         23 return $self;
248              
249             }
250              
251             =item \@objects = Class->next
252              
253             Gets next object from vfile
254              
255             =cut
256              
257             use overload
258 8         77 '<>' => \&next,
259             fallback => 1,
260 8     8   2822 ;
  8         1306  
261              
262              
263             sub next {
264              
265 20     20 1 17278 my $self=shift;
266              
267 20 100       63 if ($self->{'source_file'}) {
268 11 100       41 $self->_open unless $self->{'fh'};
269             }
270 20         45 my $fh=$self->{'fh'};
271              
272 20 100       60 if ($self->{'source_text'}) {
273 9 100       81 $self->{'text'} = [ split (/[\r\n]+/, $self->{'source_text'}) ] unless $self->{'text'};
274             }
275              
276 20 50 66     95 return () unless $fh || $self->{'text'};
277              
278             # my $parent=shift;
279             # $self->{'_parent'}=$parent if ref $parent;
280              
281 20 100       149 my $line = $fh ? <$fh> : shift @{$self->{'text'}};
  9         17  
282 20 100       62 return if $self->eod;
283            
284 15         61 my $decoder;
285              
286             # UTF-16/32 detection
287 15 50       86 if ( $line =~ /\000/ ) {
288              
289 0         0 eval "use Encode;";
290 0 0       0 die "Cannot decode this file - need the Encode module; $@\n" if $@;
291              
292 0 0       0 if ($line =~ /\000\000\000/) {
293              
294 0 0       0 if ($line =~ /^\000/) {
295 0         0 $decoder=Encode::find_encoding("UTF-32BE");
296             } else {
297 0         0 $decoder=Encode::find_encoding("UTF-32LE");
298             }
299              
300             } else {
301              
302 0 0       0 if ($line =~ /^\000/) {
303 0         0 $decoder=Encode::find_encoding("UTF-16BE");
304             } else {
305 0         0 $decoder=Encode::find_encoding("UTF-16LE");
306             }
307              
308             }
309              
310             }
311              
312 15 50       34 $line = $decoder->decode( $line ) if $decoder;
313              
314             # VFILE class detection
315             # - see BEGIN until found or return at EOD contition
316 15         61 until ( $line =~ /^BEGIN:/i ) {
317 0 0       0 $line = $fh ? <$fh> : shift @{$self->{'text'}};
  0         0  
318 0 0       0 return if $self->eod;
319 0 0       0 $line = $decoder->decode( $line ) if $decoder;
320             }
321              
322 15         41 $line =~ /^BEGIN:(.*)/i;
323 15         45 my $kind=uc $1;
324 15         28 my $class=$classMap{ $kind };
325 15 50       33 die "In parseable begin tag $line - unknown class\n" unless $class;
326              
327 15     5   896 eval "use $class";
  5         37  
  5         9  
  5         72  
328 15 50       46 die "Cannot load $class\n" if $@;
329              
330 15         55 my $varHandler=$class->varHandler;
331 15         52 my $thing=$class->new;
332              
333 15         27 my @lines=();
334 15         21 my $ended=0;
335 15         37 until ( $self->eod ) {
336              
337 90 100       215 $line = $fh ? <$fh> : shift @{$self->{'text'}};
  42         72  
338              
339 90 50       146 $line = $decoder->decode($line) if $decoder;
340 90         483 $line =~ s/[\r\n]+$//;
341              
342             # Sub object - like EVENT, etc.
343 90 50       193 if ($line =~ /^BEGIN:(.+)/) {
344             # $thing=$1;
345             # my $subclass= $classMap{uc $thing} || die "Don't know how to load ${thing}s\n";
346             # eval "use $subclass"; die $@ if $@;
347             # push @{$self->{$thing}}, $subclass->new->load($fh, $self);
348             # next;
349 0         0 die "sub object loading not done\n";
350             }
351              
352 90 100       194 if ($line =~ /^END:(.*)/) {
353 15 50       74 warn "bad end of data block - found END:$1 instead of END:" . uc $kind . "\n" unless uc $1 eq $kind;
354 15         22 $ended=1;
355 15         24 last;
356             }
357              
358 75         160 push @lines, $line;
359              
360             }
361 15 50       34 warn "premature end of data block - missing end tag\n" unless $ended;
362              
363 15         66 $thing->{'_lines'}= [ @lines ];
364              
365 15         38 while ( @lines ) {
366              
367 75         102 my $line = shift @lines;
368 75   66     354 while ( @lines && $lines[0] =~ /^\s(.*)/ ) {
369 0         0 $line .= $1;
370 0         0 shift @lines;
371             }
372              
373             # Non-typed line data
374 75 100       269 if ( $line =~ /^([\w\-]+):(.*)/ ) {
375              
376 15         48 my ($var, $data)=(uc $1, $2);
377 15         25 my $h;
378 15 50       82 if (UNIVERSAL::can( $thing, "load_$var")) {
379 0         0 $h="load_$var";
380             } else {
381 15         24 $h="load_singleText";
382             }
383 15 50       39 $h = "load_$varHandler->{$var}" if exists $varHandler->{$var};
384              
385 15         99 $thing->$h($var, undef, $data);
386 15         46 next;
387             }
388              
389             # ATTR OR Typed line data
390 60 50       245 if ( $line =~ /^([\w\-]+);([^:]*):(.*)/ ) {
391              
392 60         179 my ($var, $attr_dat, $data)=(uc $1, $2, $3);
393              
394 60         91 my %attr=();
395 60         238 map { /([^=]+)=(.*)/; push @{$attr{lc $1}}, $2 } split (/(?
  120         303  
  120         121  
  120         488  
396              
397 60         89 my $h;
398            
399 60 50       291 if (UNIVERSAL::can( $thing, "load_$var")) {
400 0         0 $h="load_$var";
401             } else {
402 60 50       142 $h = exists $attr{'type'} ? "load_singleTextTyped" : "load_singleText";
403             }
404              
405 60 50       150 $h = "load_$varHandler->{$var}" if exists $varHandler->{$var};
406              
407 60         217 $thing->$h($var, \%attr, $data);
408 60         166 next;
409             }
410              
411 0         0 $self->error( $line );
412              
413             }
414              
415 15         58 return $thing;
416              
417             }
418              
419              
420             =item $loader->eod
421              
422             Returns true if loader is at end of data for current source.
423              
424             =cut
425              
426             sub eod {
427              
428 110 100   110 1 734 if ( $_[0]->{'fh'} ) {
429 59         232 return eof $_[0]->{'fh'};
430             }
431              
432 51 100 66     119 return 0 if exists $_[0]->{'text'} && @{$_[0]->{'text'}};
  51         204  
433 2         8 return 1;
434              
435             }
436              
437             =item $object->error
438              
439             Called when a line cannot be successfully decoded
440              
441             =back
442              
443             =cut
444              
445 0     0 1 0 sub error { warn ref($_[0]) . " ERRORLINE: $_[1]\n"; }
446              
447             =head1 SUPPORT
448              
449             For technical support please email to jlawrenc@cpan.org ...
450             for faster service please include "Text::vFile" and "help" in your subject line.
451              
452             =head1 AUTHOR
453              
454             Jay J. Lawrence - jlawrenc@cpan.org
455             Infonium Inc., Canada
456             http://www.infonium.ca/
457              
458             =head1 COPYRIGHT
459              
460             Copyright (c) 2003 Jay J. Lawrence, Infonium Inc. All rights reserved.
461             This program is free software; you can redistribute
462             it and/or modify it under the same terms as Perl itself.
463              
464             The full text of the license can be found in the
465             LICENSE file included with this module.
466              
467             =head1 ACKNOWLEDGEMENTS
468              
469             Leo - for a very productive exchange on how this should work plus suffering
470             through a few growing pains.
471              
472             Net::iCal - whose loading code inspired me for mine
473              
474             =head1 SEE ALSO
475              
476             RFC 2425, 2426, 2445
477              
478             =cut
479              
480             1;
481