File Coverage

blib/lib/XML/SAXDriver/vCard.pm
Criterion Covered Total %
statement 195 252 77.3
branch 98 140 70.0
condition 4 9 44.4
subroutine 16 17 94.1
pod 5 5 100.0
total 318 423 75.1


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             XML::SAXDriver::vCard - generate SAX2 events for vCard 3.0
4              
5             =head1 SYNOPSIS
6              
7             use XML::SAX::Writer;
8             use XML::SAXDriver::vCard;
9              
10             my $writer = XML::SAX::Writer->new();
11             my $driver = XML::SAXDriver::vCard->new(Handler=>$writer);
12              
13             $driver->parse_file("test.vcd");
14              
15             =head1 DESCRIPTION
16              
17             Generate SAX2 events for vCard 3.0
18              
19             =cut
20              
21 3     3   5750 use strict;
  3         8  
  3         163  
22              
23             package XML::SAXDriver::vCard;
24 3     3   15 use base qw (XML::SAX::Base);
  3         7  
  3         5874  
25              
26             $XML::SAXDriver::vCard::VERSION = '0.05';
27              
28 3         231 use constant NS => {
29             "VCARD" => "http://www.ietf.org/internet-drafts/draft-dawson-vCard-xml-dtd-04.txt",
30 3     3   113322 };
  3         7  
31              
32 3     3   15 use constant VCARD_VERSION => "3.0";
  3         4  
  3         11377  
33              
34             # Can someone please tell me how to derefence constants
35             # in regular expressions. You can't, can you....
36             my $regexp_type = qr/(?:;(TYPE=(?:(?:\\:|[^:])+)?)?)/i;
37              
38             =head1 PACKAGE METHODS
39              
40             =head2 __PACKAGE__->new(%args)
41              
42             This method is inherited from I
43              
44             =cut
45              
46             =head1 OBJECT METHODS
47              
48             =head2 $pkg->parse($string)
49              
50             =cut
51              
52             sub parse {
53 1     1 1 140027 my $self = shift;
54 1         3 my $str = shift;
55              
56 1 50       5 if (! $str) {
57 0         0 die "Nothing to parse.\n";
58             }
59              
60 1         8 $self->start_document();
61 1         5 $self->_parse_str($str);
62 1         4 $self->end_document();
63              
64 1         8 return 1;
65             }
66              
67             =head2 $pkg->parse_file($path)
68              
69             =cut
70              
71             sub parse_file {
72 2     2 1 114325 my $self = shift;
73 2         6 my $vcard = shift;
74              
75 2         8 $vcard =~ s/file:\/\///;
76              
77 2         17 require FileHandle;
78 2   50     14 my $fh = FileHandle->new($vcard)
79             || die "Can't open '$vcard', $!\n";
80              
81 2         162 $self->start_document();
82 2         8 $self->_parse_file(\*$fh);
83 2         8 $self->end_document();
84              
85 2         47 return 1;
86             }
87              
88             =head2 $pkg->parse_uri($uri)
89              
90             =cut
91              
92             sub parse_uri {
93 2     2 1 156275 my $self = shift;
94 2         7 my $uri = shift;
95              
96 2 100       13 if ($uri =~ /^file:\/\//) {
97 1         8 return $self->parse_file($uri);
98             }
99              
100 1         12 require LWP::Simple;
101              
102 1 50       8 if (! LWP::Simple::head($uri)) {
103 1         188722 die "Unable to retreive remote vCard : ".getprint($uri)."\n";
104             }
105              
106 0         0 return $self->parse(LWP::Simple::get($uri));
107             }
108              
109             # Private methods
110              
111             # Section 2.4.2 for discussion of chunks
112              
113             sub _parse_str {
114 2     2   4 my $self = shift;
115 2         4 my $str = shift;
116              
117 2         6 my %card = ();
118              
119 2         17 foreach (split("\n",$str)) {
120              
121 40 100       98 if (! $self->_parse_ln($_,\%card)) {
122 3         29 %card = ();
123             }
124             }
125              
126 2         36 return 1;
127             }
128              
129             sub _parse_file {
130 2     2   4 my $self = shift;
131 2         4 my $fh = shift;
132              
133 2         6 my %card = ();
134              
135 2         10 while (! $fh->eof()) {
136 28         858 my $ln = $fh->getline();
137 28         798 chomp $ln;
138              
139 28 100       69 if (! $self->_parse_ln($ln,\%card)) {
140 2         26 %card = ();
141             }
142             }
143              
144 2         59 return 1;
145             }
146              
147             sub _parse_ln {
148 68     68   82 my $self = shift;
149 68         88 my $ln = shift;
150 68         79 my $vcard = shift;
151              
152             # Danger, Will Robinson! Un-SAX like behaviour ahead.
153              
154             # Specifically, we are going tostore record data in a
155             # private hash ref belonging to the
156             # object. I am not happy about this either, however we have to
157             # do this because the vCard UID property is mapped to XML as
158             # an attribute of the vcard element. Since we have no idea
159             # where the UID property will be in the vCard -- it will probably
160             # be near the bottom of the record -- we have to postpone any
161             # writing until we get to it. There is always the possibility
162             # that property won't be defined but... Anyway, there are other
163             # properties that are mapped to vcard@foo so in an effort to keep
164             # the code (relatively) small and clean I've opted for caching
165             # everything and writing it all out when the 'END:vCard thingy
166             # is reached. It occured to me to write the (XML) data once, cache
167             # only a small set of properties and then add them at the end
168             # using XML::SAX::Merger. Ultimately, I decided that was crazy-talk.
169              
170             # These are the properties you are looking for.
171              
172 68 100       214 if ($ln =~ /^[DHIJQVWYZ]/i) {
    100          
173 4         20 return 1;
174             }
175              
176             # AGENT properties are parsed separately when the current vCard
177             # is rendered. So we'll just keep track of the agent's vcard data
178             # as a big ol' string.
179              
180             elsif ($vcard->{'__isagent'}) {
181 4         11 $vcard->{agent}{vcard} .= $ln."\n";
182 4 100       11 if ($ln =~ /^EN/i) { $vcard->{'__isagent'} = 0; }
  1         2  
183 4         13 return 1;
184             }
185              
186             else {}
187              
188             # SOURCE
189 60 50       383 if ($ln =~ /^SOUR/i) {
    100          
    100          
    100          
    100          
    100          
190 0         0 $ln =~ /^SOURCE:(.*)/i;
191 0         0 $vcard->{source} = $1;
192             }
193              
194             # FN
195             elsif ($ln =~ /^F/i) {
196 5         19 $ln =~ /^FN:(.*)$/i;
197 5         26 $vcard->{fn} = $1;
198             }
199              
200             # N
201             elsif ($ln =~ /^N:/i) {
202             # Family Name, Given Name, Additional Names,
203             # Honorific Prefixes, and Honorific Suffixes.
204 4         27 $ln =~ /^N:([^;]+)?;([^;]+)?;([^;]+)?;([^;]+)?;([^;]+)?$/i;
205 4         46 $vcard->{n} = {family=>$1,given=>$2,other=>$3,prefixes=>$4,suffixes=>$5};
206             }
207              
208             # NICKNAME
209             elsif ($ln =~ /^NI/i) {
210 4         14 $ln =~ /^NICKNAME:(.*)$/i;
211 4         15 $vcard->{nickname} = $1;
212             }
213              
214             # PHOTO
215             elsif ($ln =~ /^PHOT/i) {
216 4         20 $ln =~ /^PHOTO;(?:VALUE=uri:(.*)|ENCODING=b;TYPE=([^:]+):(.*))$/i;
217 4 50       39 $vcard->{photo} = ($2) ? {type=>$1,b64=>$2} : {url=>$1};
218             }
219              
220             # BDAY
221             elsif ($ln =~ /^BD/i) {
222 4         12 $ln =~ /^BDAY:(.*)$/i;
223 4         24 $vcard->{bday} = $1;
224             }
225              
226             # ADR
227             # Mulitple ADR 'TYPE's may be defined using either as
228             # a parameter list or a value list.
229              
230 60 100       953 if ($ln =~ /^AD/i) {
    50          
    100          
    100          
    50          
    50          
    50          
    100          
    50          
    50          
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    50          
    50          
    100          
231 5         253 $ln =~ /^ADR$regexp_type:([^;]+)?;([^;]+)?;([^;]+)?;([^;]+)?;([^;]+)?;([^;]+)?;([^;]+)?$/i;
232 5         9 push @{$vcard->{adr}} , {"type"=>$1,pobox=>$2,extadr=>$3,street=>$4,locality=>$5,region=>$6,pcode=>$7,country=>$8};
  5         75  
233             }
234              
235             # LABEL
236             elsif ($ln =~ /^L/i) {
237             }
238              
239             # TEL
240             elsif ($ln =~ /^TE/i) {
241 5         91 $ln =~ /^TEL$regexp_type?:(.*)$/i;
242 5         8 push @{$vcard->{tel}},{"type"=>$1,number=>$2};
  5         67  
243             }
244              
245             # EMAIL
246             elsif ($ln =~ /^EM/i) {
247 1         44 $ln =~ /^EMAIL$regexp_type?:(.*)$/i;
248 1   50     3 push @{$vcard->{email}},{"type"=>($1 || "internet"),address=>$2};
  1         13  
249             }
250              
251             # MAILER
252             elsif ($ln =~ /^M/i) {
253 0         0 $ln =~ /^MAILER;(.*)$/i;
254 0         0 $vcard->{mailer} = $1;
255             }
256              
257             # TZ
258             elsif ($ln =~ /^TZ/i) {
259 0         0 $ln =~ /^TZ:(?:VALUE=([^:]+):)?(.*)$/i;
260 0         0 $vcard->{tz} = $1;
261             }
262              
263             # GEO
264             elsif ($ln =~ /^G/i) {
265 0         0 $ln =~ /^GEO:([^;]+);(.*)$/i;
266 0         0 $vcard->{geo} = {lat=>$1,lon=>$2};
267             }
268              
269             # TITLE
270             elsif ($ln =~ /^TI/i) {
271 4         14 $ln =~ /^TITLE:(.*)$/i;
272 4         14 $vcard->{title} = $1;
273             }
274              
275             # ROLE
276             elsif ($ln =~ /^R/i) {
277 0         0 $ln =~ /^ROLE:(.*)$/i;
278 0         0 $vcard->{role} = $1;
279             }
280              
281             # LOGO
282             elsif ($ln =~ /^L/i) {
283 0         0 $ln =~ /^LOGO;(?:VALUE=(.*)|ENCODING=b;TYPE=([^:]+):(.*))$/i;
284 0 0       0 $vcard->{logo} = ($2) ? {type=>$1,b64=>$2} : {url=>$1};
285             }
286              
287             # AGENT
288             elsif ($ln =~ /^AG/i) {
289 1         5 $ln =~ /^AGENT(;VALUE=uri)?:(.*)$/i;
290              
291 1 50       5 if ($1) {
292 0         0 $vcard->{agent}{'uri'} = $2;
293             }
294              
295 1         3 $vcard->{'__isagent'} = 1;
296              
297             # Note the '.='
298             # It is possible that we are dealing
299             # with nested AGENT properties. Ugh.
300 1         6 $vcard->{agent}{vcard} .= "$2\n";
301             }
302              
303             # ORG
304             elsif ($ln =~ /^O/i) {
305 4         16 $ln =~ /^ORG:([^;]+);([^;]+);(.*)$/i;
306 4         21 $vcard->{org} = {name=>$1,unit=>$2};
307             }
308              
309             # CATEGORIES
310             elsif ($ln =~ /^CA/i) {
311 4         14 $ln =~ /^CATEGORIES:(.*)$/i;
312 4         21 $vcard->{categories} = [split(",",$1)];
313             }
314              
315             # NOTE
316             elsif ($ln =~ /^NO/i) {
317 0         0 $ln =~ /^NOTE:(.*)$/i;
318 0         0 $vcard->{note} = $1;
319             }
320              
321             # PRODID
322             elsif ($ln =~ /^PR/i) {
323 0         0 $ln =~ /^PRODID:(.*)$/i;
324 0         0 $vcard->{prodid} = $1;
325             }
326              
327             # REV
328             elsif ($ln =~ /^RE/i) {
329 0         0 $ln =~ /^REV:(.*)$/i;
330 0         0 $vcard->{rev} = $1;
331             }
332              
333             # SORT-STRING
334             elsif ($ln =~ /^SOR/i) {
335 0         0 $ln =~ /^SORT-STRING:(.*)/i;
336 0         0 $vcard->{'sort'} = $1;
337             }
338              
339             # SOUND
340             elsif ($ln =~ /^SOUN/i) {
341 0         0 $ln =~ /^SOUND:TYPE=BASIC;(VALUE|ENCODING)=([buri]):(.*)$/i;
342 0 0       0 $vcard->{'sound'} = ($1 eq "VALUE") ? {uri=>$2} : {b64=>$2};
343             }
344              
345             # UID
346             elsif ($ln =~ /^UI/i) {
347 4         15 $ln =~ /^UID:(.*)$/i;
348 4         34 $vcard->{uid} = $1;
349             }
350              
351             # URL
352             elsif ($ln =~ /^UR/i) {
353 0         0 $ln =~ /^URL:(.*)$/i;
354 0         0 push @{$vcard->{url}},$1;
  0         0  
355             }
356              
357             # CLASS
358             elsif ($ln =~ /^CL/i) {
359 0         0 $ln =~ /^CLASS:(.*)$/i;
360 0         0 $vcard->{class} = $1;
361             }
362              
363             # KEY
364             elsif ($ln =~ /^K/i) {
365 0         0 $ln =~ /^KEY;ENCODING=b:(.*)$/i;
366 0         0 $vcard->{'key'} = $1;
367             }
368              
369             # X-CUSTOM
370             elsif ($ln =~ /^X/i) {
371 0         0 $ln =~ /^X-CUSTOM;([^:]+):(.*)$/i;
372 0         0 push @{$vcard->{'x-custom'}}, {$1=>$2};
  0         0  
373             }
374              
375             # END:vCard
376             elsif ($ln =~ /^EN/i) {
377 5         24 $self->_saxify($vcard);
378              
379             # We return 0 explicitly since that
380             # is the signal to the calling method
381             # that %$vcard should be emptied.
382 5         18 return 0;
383             }
384              
385 55         232 return 1
386             }
387              
388             sub start_document {
389 3     3 1 6 my $self = shift;
390              
391 3         28 $self->SUPER::start_document();
392 3         179 $self->SUPER::xml_decl({Version=>"1.0"});
393             # Add DOCTYPE stuff for X-LABEL here
394 3         144 $self->start_prefix_mapping({Prefix=>"",NamespaceURI=>NS->{VCARD}});
395 3         145 $self->SUPER::start_element({Name=>"vCardSet"});
396 3         122 return 1;
397             }
398              
399             sub end_document {
400 3     3 1 5 my $self = shift;
401              
402 3         18 $self->SUPER::end_element({Name=>"vCardSet"});
403 3         57 $self->end_prefix_mapping({Prefix=>""});
404 3         137 $self->SUPER::end_document();
405 3         124 return 1;
406             }
407              
408             sub _saxify {
409 5     5   17 my $self = shift;
410 5         7 my $vcard = shift;
411              
412             # See also : comments in &_parse()
413              
414 5   50     55 my $attrs = {
415             "{}version" => {Name=>"version",
416             Value=>VCARD_VERSION},
417             "{}class"=>{Name=>"class",
418             Value=>($vcard->{class} || "PUBLIC")},
419             };
420              
421 5         13 foreach ("uid","lang","rev","prodid") {
422 20 100       54 if (exists($vcard->{$_})) {
423 4         22 $attrs->{"{}$_"} = {Name=>$_,
424             Value=>$vcard->{$_}};
425             }
426             }
427              
428             #
429              
430 5         48 $self->SUPER::start_element({Name=>"vCard",Attributes=>$attrs});
431              
432             #
433              
434             # FN:
435 5         89 $self->_pcdata({name=>"fn",value=>$vcard->{'fn'}});
436              
437             # N:
438 5         25 $self->SUPER::start_element({Name=>"n"});
439              
440 5         54 foreach ("family","given","other","prefix","suffix") {
441 25         116 $self->_pcdata({name=>$_,value=>$vcard->{'n'}{$_}});
442             }
443              
444 5         43 $self->SUPER::end_element({Name=>"n"});
445              
446             # NICKNAME:
447 5 100       57 if (exists($vcard->{'nickname'})) {
448 4         31 $self->_pcdata({name=>"nickname",value=>$vcard->{'nickname'}});
449             }
450              
451             # PHOTO:
452 5 100       22 if (exists($vcard->{'photo'})) {
453 4         14 $self->_media({name=>"photo",%{$vcard->{photo}}});
  4         28  
454             }
455              
456             # BDAY:
457 5 100       20 if (exists($vcard->{'bday'})) {
458 4         22 $self->_pcdata({name=>"bday",value=>$vcard->{'bday'}});
459             }
460              
461             # ADR:
462 5 100       23 if (ref($vcard->{'adr'}) eq "ARRAY") {
463 4         6 foreach my $adr (@{$vcard->{'adr'}}) {
  4         11  
464              
465 5         26 &_munge_type(\$adr->{type});
466              
467 5         41 $self->SUPER::start_element({Name=>"adr",
468             Attributes=>{"{}del.type"=>{Name=>"del.type",Value=>$adr->{type}}}
469             });
470              
471 5         60 foreach ("pobox","extadr","street","locality","region","pcode","country") {
472 35         214 $self->_pcdata({name=>$_,value=>$adr->{$_}});
473             }
474              
475 5         60 $self->SUPER::end_element({Name=>"adr"});
476             }
477             }
478              
479             # LABEL
480             # $self->label();
481              
482 5 50       63 if (ref($vcard->{'tel'}) eq "ARRAY") {
483              
484 5         8 foreach my $t (@{$vcard->{'tel'}}) {
  5         19  
485 5         16 &_munge_type(\$t->{type});
486              
487 5         38 $self->_pcdata({name=>"tel",value=>$t->{number},
488             attrs=>{"{}tel.type"=>{Name=>"tel.type",Value=>$t->{type}}}
489             });
490             }
491             }
492              
493             # EMAIL:
494              
495 5 100       23 if (ref($vcard->{'email'}) eq "ARRAY") {
496              
497 1         2 foreach my $e (@{$vcard->{'email'}}) {
  1         3  
498 1         4 &_munge_type(\$e->{type});
499              
500 1         9 $self->_pcdata({name=>"email",value=>$e->{address},
501             attrs=>{"{}email.type"=>{Name=>"email.type",Value=>$e->{type}}}
502             });
503             }
504             }
505              
506             # MAILER:
507 5 50       17 if (exists($vcard->{'mailer'})) {
508 0         0 $self->_pcdata({name=>"mailer",
509             value=>$vcard->{'mailer'}});
510             }
511              
512             # TZ:
513 5 50       15 if (exists($vcard->{'tz'})) {
514 0         0 $self->_pcdata({name=>"tz",
515             value=>$vcard->{'tz'}});
516             }
517              
518             # GEO:
519 5 50       25 if (exists($vcard->{'geo'})) {
520 0         0 $self->SUPER::start_element({Name=>"geo"});
521 0         0 $self->_pcdata({name=>"lat",value=>$vcard->{'geo'}{'lat'}});
522 0         0 $self->_pcdata({name=>"lon",value=>$vcard->{'geo'}{'lon'}});
523 0         0 $self->SUPER::end_element({Name=>"geo"});
524             }
525              
526             # TITLE:
527 5 100       17 if (exists($vcard->{'title'})) {
528 4         21 $self->_pcdata({name=>"title",value=>$vcard->{'title'}});
529             }
530              
531             # ROLE
532 5 50       22 if (exists($vcard->{'role'})) {
533 0         0 $self->_pcdata({name=>"role",value=>$vcard->{'role'}});
534             }
535              
536             # LOGO:
537 5 50       17 if (exists($vcard->{'logo'})) {
538 0         0 $self->_media({name=>"logo",%{$vcard->{'logo'}}});
  0         0  
539             }
540              
541             # AGENT:
542 5 100       24 if (exists($vcard->{agent})) {
543 1         7 $self->SUPER::start_element({Name=>"agent"});
544              
545 1 50       15 if ($vcard->{agent}{uri}) {
546 0         0 $self->_pcdata({name=>"extref",attrs=>{"{}uri"=>{Name=>"uri",
547             Value=>$vcard->{'agent'}{'uri'}}}
548             });
549             }
550              
551             else {
552 1         7 $self->_parse_str($vcard->{agent}{vcard});
553             }
554              
555 1         23 $self->SUPER::end_element({Name=>"agent"});
556             }
557              
558             # ORG:
559 5 100       36 if (exists($vcard->{'org'})) {
560 4         22 $self->SUPER::start_element({Name=>"org"});
561 4         56 $self->_pcdata({name=>"orgnam",value=>$vcard->{'org'}{'name'}});
562 4         21 $self->_pcdata({name=>"orgunit",value=>$vcard->{'org'}{'unit'}});
563 4         22 $self->SUPER::end_element({Name=>"org"});
564             }
565              
566             # CATEGORIES:
567 5 100       53 if (ref($vcard->{'categories'}) eq "ARRAY") {
568 4         20 $self->SUPER::start_element({Name=>"categories"});
569 4         42 foreach (@{$vcard->{categories}}) {
  4         13  
570 8         60 $self->_pcdata({name=>"item",value=>$_});
571             }
572 4         22 $self->SUPER::end_element({Name=>"categories"});
573             }
574              
575             # NOTE:
576 5 50       54 if (exists($vcard->{'note'})) {
577 0         0 $self->_pcdata({name=>"note",value=>$vcard->{'note'}});
578             }
579              
580             # SORT:
581 5 50       17 if (exists($vcard->{'sort'})) {
582 0         0 $self->_pcdata({name=>"sort",value=>$vcard->{'sort'}});
583             }
584              
585             # SOUND:
586 5 50       14 if (exists($vcard->{'sound'})) {
587 0         0 $self->_media({name=>"sound",%{$vcard->{'sound'}}});
  0         0  
588             }
589              
590             # URL:
591 5 50       16 if (ref($vcard->{'url'}) eq "ARRAY") {
592 0         0 foreach (@{$vcard->{'url'}}) {
  0         0  
593 0         0 $self->_pcdata({name=>"url",
594             Attributes=>{"{}uri"=>{Name=>"uri",Value=>$_}}});
595             }
596             }
597              
598             # KEY:
599 5 50       14 if (exists($vcard->{'key'})) {
600 0         0 $self->_media($vcard->{key});
601             }
602              
603             # $self->xcustom();
604              
605 5         24 $self->SUPER::end_element({Name=>"vCard"});
606              
607 5         65 return 1;
608             }
609              
610             sub _pcdata {
611 103     103   144 my $self = shift;
612 103         121 my $data = shift;
613 103         483 $self->SUPER::start_element({Name=>$data->{name},Attributes=>$data->{attrs}});
614 103 50       1129 $self->SUPER::start_cdata() if ($data->{cdata});
615 103         432 $self->SUPER::characters({Data=>$data->{value}});
616 103 50       1217 $self->SUPER::end_cdata() if ($data->{cdata});
617 103         471 $self->SUPER::end_element({Name=>$data->{name}});
618 103         1190 return 1;
619             }
620              
621             sub _media {
622 4     4   7 my $self = shift;
623 4         7 my $data = shift;
624              
625 4         14 my $attrs = {};
626              
627             # as in not 'key' and not something pointing to an 'uri'
628 4 50 33     20 if ((! $data->{name} =~ /^k/) && ($data->{type})) {
629              
630             # as in 'photo' or 'logo' and not 'sound'
631 0 0       0 my $mime = ($data->{name} =~ /^[pl]/i) ? "img" : "aud";
632 0         0 $attrs = {"{}$mime.type"=>{Name=>"$mime.type",Value=>$data->{type}}};
633             }
634              
635 4         29 $self->SUPER::start_element({Name=>$data->{name},Attributes=>$attrs});
636              
637 4 50       47 if ($data->{url}) {
638 4         30 $self->_pcdata({name=>"extref",attrs=>{"{}uri"=>{Name=>"uri",
639             Value=>$data->{url}}}
640             });
641             }
642              
643             else {
644 0         0 $self->_pcdata({name=>"b64bin",value=>$data->{b64},cdata=>1});
645             }
646              
647 4         25 $self->SUPER::end_element({Name=>$data->{name}});
648 4         42 return 1;
649             }
650              
651             # Convert all type data into a value list
652              
653             sub _munge_type {
654 11     11   14 my $sr_str = shift;
655 11 100       26 $$sr_str || return;
656              
657             # Remove the leading TYPE=
658             # declaration: see also $regexp_type
659 10         37 $$sr_str =~ s/^TYPE=//i;
660              
661             # Remove any subsequent TYPE=
662             # thingies and replace them
663             # with commas
664 10         26 $$sr_str =~ s/;TYPE=/,/gi;
665             }
666              
667 0     0     sub DESTROY {}
668              
669             =head1 VERSION
670              
671             0.05
672              
673             =head1 DATE
674              
675             February 18, 2003
676              
677             =head1 AUTHOR
678              
679             Aaron Straup Cope
680              
681             =head1 NOTES
682              
683             =head2 What about representing vCard objects in RDF/XML?
684              
685             It's not going to happen here.
686              
687             I might write a pair of vcard-rdfxml <-> vcard-xml filters in the
688             future. If you're chomping at the bit to do this yourself, please,
689             go nuts.
690              
691             =head1 TO DO
692              
693             =over 4
694              
695             =item *
696              
697             Better (proper) support for properties that span multiple lines. See also:
698              
699             section 5.8.1. Line delimiting and folding (RFC 2425)
700             section 2.6 Line Delimiting and Folding (RFC 2426)
701              
702             I
703              
704             =item *
705              
706             Wrap lines at 75 chars for media thingies.
707              
708             I
709              
710             =item *
711              
712             Better checks to prevent empty elements from being include in final
713             output.
714              
715             =item *
716              
717             Add support for I
718              
719             =item *
720              
721             Add support for I properties. These are not actually defined
722             in the vcard-xml DTD :-(
723              
724             =item *
725              
726             Add support for pronounciation attribute extension
727              
728             =back
729              
730             =head1 SEE ALSO
731              
732             http://www.ietf.org/rfc/rfc2426.txt
733              
734             http://www.ietf.org/rfc/rfc2425.txt
735              
736             http://www.globecom.net/ietf/draft/draft-dawson-vcard-xml-dtd-03.html
737              
738             http://www.imc.org/pdi/vcard-pronunciation.html
739              
740             http://www.w3.org/TR/vcard-rdf
741              
742             =head1 BUGS
743              
744             Sadly, there are probably a few.
745              
746             Please report all bugs via http://rt.cpan.org
747              
748             =head1 LICENSE
749              
750             Copyright (c) 2002-2003, Aaron Straup Cope. All Rights Reserved.
751              
752             This is free software, you may use it and distribute it under the same terms as Perl itself.
753              
754             =cut
755              
756             return 1;
757