55efc3be98f49cc3de0d0f197cba4f898ef3d899
[matthijs/upstream/blosxom-plugins.git] / gavinc / uf_hcard_meta
1 # Blosxom Plugin: uf_hcard_meta
2 # Author(s): Gavin Carr <gavin@openfusion.com.au>
3 # Version: 0.001000
4 # Documentation: 'perldoc uf_hcard_meta'
5
6 package uf_hcard_meta;
7
8 use strict;
9
10 # Uncomment next line to enable debug output (don't uncomment debug() lines)
11 #use Blosxom::Debug debug_level => 2;
12
13 # --- Configuration defaults -----
14
15 my %config = (
16
17   # Extra CSS classes to add to the microformat container e.g. to turn display off
18   class => '',
19   #class => 'nodisplay',
20
21   # Whether to automatically add microformat to story bodies. If not set, 
22   # you must explicitly add $uf_adr_meta::adr to a template somewhere.
23   auto_append_to_body => 1,
24
25   # What markup style to use for your adr, if auto-appending. 
26   # 3 styles are currently defined: 
27   # 'div-span' uses a 'div' elt for the container, and 'span' elements for the fields
28   # 'ul' uses a 'ul' list for the container, and 'li' elements for the fields
29   # 'dl' uses a 'dl' list for the container, 'dt' elements for field names, and 
30   #    'dd' elements for the fields themselves
31   #style => 'div-span',
32   #style => 'ul',
33   style => 'dl',
34
35 );
36
37 # --------------------------------
38
39 use vars qw($hcard);
40
41 # Official hcard attributes
42 my @req_attr = qw(fn);
43 my %opt_attr = (
44     'adr'         => [ qw(address-type post-office-box extended-address street-address
45                           locality region postal-code country-name) ],
46     'agent'       => 1,
47     'bday'        => 1,
48     'email'       => 1,
49     'geo'         => [ qw(latitude longitude) ],
50     'key'         => 1,
51     'label'       => 1,
52     'logo'        => 1,
53     'mailer'      => 1,
54     'n'           => [ qw(honorific-prefix given-name additional-name family-name honorific-suffix) ],
55     'nickname'    => 1,
56     'note'        => 1,
57     'org'         => [ qw(organization-name organization-unit) ],
58     'photo'       => 1,
59     'rev'         => 1,
60     'role'        => 1,
61     'sort-string' => 1,
62     'sound'       => 1,
63     'tel'         => 1,
64     'tz'          => 1,
65     'uid'         => 1,
66     'url'         => 1,
67 );
68   
69 # Attribute aliases
70 my %alias = (
71     'fn'                    => [ 'name' ],
72     'email-value'           => [ 'email' ],
73     'tel'                   => [ qw(telephone phone) ],
74     'organization-name'     => [ qw(org organisation organization) ],
75     'post-office-box'       => [ 'pobox' ],
76     'street-address'        => [ 'street' ],
77     'locality'              => [ 'suburb', 'city' ],
78     'region'                => [ 'state' ],
79     'postal-code'           => [ 'postcode' ],
80     'country-name'          => [ 'country' ],
81 );
82 my %attr_types = map { $_ => 1 } qw(tel);
83 my %attr_types_standalone = (
84     tel                     => { map { $_ => 1 } qw(fax cell mobile) },
85 );
86 my %alias_types = (
87     mobile                  => 'cell',
88 );
89
90 $config{style} = 'div-span' unless $config{style} eq 'ul' or $config{style} eq 'dl';
91
92 sub start { 1 }
93
94 # Return the first existing metadata item key and value given a list of keys
95 sub _get_meta {
96     for my $attr ( @_ ) {
97         my $meta_attr = $attr;
98         $meta_attr =~ s/-/_/g;
99         my $value = $blosxom::meta{$meta_attr};
100         $value = eval "\$meta::$attr" unless defined $value;
101         return wantarray ? ( $attr, $value ) : $value if defined $value;
102     }
103     return wantarray ? () : undef;
104 }
105
106 sub _add_attr {
107     my ($hcard, $attr, $value, $style, $parent_attr, $parent_started, $type) = @_;
108
109     # Start parent if set and not started
110     if ($parent_attr && (! defined $parent_started || ! $$parent_started)) {
111         $$hcard .= qq(<span class="$parent_attr">\n);
112         $$parent_started = 1 if defined $parent_started;
113     }
114
115     # Append hcard output
116     my $label = '';
117     if ($type) {
118         $label = join ', ', map { qq(<span class="type">$_</span>) } split /[_\W]+/, $type;
119     }
120     elsif ($attr =~ m/^(\w+)-(value)$/) {
121         $label = $1;
122         $attr = $2;
123     } 
124     elsif ($style eq 'dl') {
125         $label = $attr;
126         $label =~ s/\s+/-/g;
127     }
128     my $type_string;
129     if ($style eq 'dl') {
130         $$hcard .= qq(<dt>$label</dt>);
131         $type_string = '';
132     }
133     elsif ($label) {
134         $type_string = "($label) ";
135     }
136     my $etag = $style eq 'div-span' ? 'span' : 
137                $style eq 'ul' ? 'li' : 'dd';
138     $$hcard .= qq(<$etag class="$attr">$type_string$value</$etag>\n);
139
140     # Close parent unless we have a $parent_started flag to set
141     $$hcard .= qq(</span>\n) if $parent_attr && ! defined $parent_started;
142 }
143
144 sub story {
145     my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
146
147     # Skip unless all required attributes are set
148     for (@req_attr) {
149         my $value = _get_meta($_, $alias{$_} ? @{$alias{$_}} : ());
150         unless ($value) {
151             # debug(2, "No name attribute found in $path/$filename - skipping post");
152             return 1;
153         }
154     }
155
156     my $story_style = _get_meta( 'hcard_style' ) || $config{style};
157     my $ctag = $story_style eq 'div-span' ? 'div' : $story_style;
158
159     $hcard = '';
160     my $container_classes = 'vcard';
161     if (my $meta_class = _get_meta('hcard_class')) {
162       $container_classes .= " $meta_class";
163     }
164     else {
165       $container_classes .= " $config{class}" if $config{class};
166     }
167     $hcard .= qq(<$ctag class="$container_classes">\n);
168     for my $attr ( @req_attr ) {
169         my $value = _get_meta( $attr, $alias{$attr} ? @{$alias{$attr}} : () );
170         _add_attr(\$hcard, $attr, $value, $story_style) if $value;
171     }
172     for my $attr ( sort keys %opt_attr ) {
173         # Allow nested attributes
174         if (ref $opt_attr{$attr}) {
175             my $parent_attr = $attr;
176             my $parent_started = 0;
177             for $attr ( @{ $opt_attr{$parent_attr} } ) {
178                 my $value = _get_meta( $attr, $alias{$attr} ? @{$alias{$attr}} : () );
179                 _add_attr(\$hcard, $attr, $value, $story_style, $parent_attr, \$parent_started)
180                     if $value;
181             }
182             if ($parent_started) {
183                 $hcard .= qq(</span>\n);
184                 next;
185             }
186         }
187     
188         # Allow bare attributes and aliases
189         my $value = _get_meta( $attr, $alias{$attr} ? @{$alias{$attr}} : () );
190         _add_attr(\$hcard, $attr, $value, $story_style) if $value;
191
192         # Allow typed attributes 
193         if ($attr_types{ $attr }) {
194             # TODO: need to support $meta package variables here too
195             for my $meta (sort keys %blosxom::meta) {
196                 # Search for all metadata beginning with $attr or aliases
197                 for my $a ( $attr, $alias{$attr} ? @{$alias{$attr}} : () ) {
198                     if (lc $meta =~ m/^$a[^a-z]+(\w+)$/) {
199                         _add_attr(\$hcard, 'value', $blosxom::meta{ $meta }, $story_style, $attr, undef, $1);
200                     }
201                 }
202             }
203
204             # Allow standalone types
205             for my $type ( sort keys %{ $attr_types_standalone{ $attr } } ) {
206                 if (my $value = _get_meta( $type )) {
207                     _add_attr(\$hcard, 'value', $value, $story_style, $attr, undef, 
208                               $alias_types{ $type } || $type);
209                 }
210
211             }
212         }
213     }
214     $hcard .= qq(</$ctag>\n);
215     # debug(2, "hcard $hcard\n");
216
217     my $autoappend = _get_meta( 'hcard_autoappend' );
218     $autoappend = $config{auto_append_to_body} unless defined $autoappend;
219     return 1 unless $autoappend;
220
221     $$body_ref .= "\n\n$hcard\n\n";
222
223     return 1;
224 }
225
226 1;
227
228 __END__
229
230 =head1 NAME
231
232 uf_hcard_meta - plugin to create an 'hcard' microformat tag from post 
233 metadata
234
235 =head1 DESCRIPTION
236
237 uf_hcard_meta is a plugin to create an 'hcard' microformat tag from 
238 metadata in your post. The microformat tag is created in the 
239 $uf_hcard_meta::hcard story variable for use in templates or by other 
240 plugins, or if the 'auto_append_to_body' config variable is set (it is 
241 by default), uf_hcard_meta will append the tag to your story body 
242 automatically.
243
244 The following metadata items are supported. By and large the official
245 hcard attribute name is supported, and sometimes one or aliases 
246 (labelled alt below) may also be supported. See the hcard definition at
247 http://www.microformats.org/wiki/hcard for definitions and discussion
248 of attributes and usage.
249
250 =head2 REQUIRED METADATA ITEMS
251
252 =over 4
253
254 =item fn (alt: name) - full name
255
256 =back
257
258 =head2 OPTIONAL METADATA ITEMS
259
260 =over 4
261
262 =item address-type
263
264 =item post-office-box (alt: pobox)
265
266 =item extended-address 
267
268 =item street-address (alt: street)
269
270 =item locality (alt: suburb, city)
271
272 =item region (alt: state)
273
274 =item postal-code (alt: postcode)
275
276 =item country-name (alt: country)
277
278 =item agent
279
280 =item bday
281
282 =item cell (alt: mobile)
283
284 =item email
285
286 =item fax
287
288 =item key
289
290 =item label
291
292 =item latitude
293
294 =item longitude
295
296 =item logo
297
298 =item mailer
299
300 =item honorific-prefix
301
302 =item given-name
303
304 =item additional-name
305
306 =item family-name
307
308 =item honorific-suffix
309
310 =item nickname
311
312 =item note
313
314 =item organization-name (alt: org, organization, organisation)
315
316 =item organization-unit
317
318 =item photo
319
320 =item rev
321
322 =item role
323
324 =item sort-string
325
326 =item sound
327
328 =item tel (alt: telephone, phone)
329
330 And see also the Telephone Element Handling section below.
331
332 =item tz
333
334 =item uid
335
336 =item uri
337
338 =back
339
340 =head2 Telephone Element Handling
341
342 hcard telephone numbers may have type attributes, and because most people have
343 multiple telephone numbers, uf_hcard_meta also supports decorating the 'tel' 
344 element (or its aliases) with one or more hcard telephone types, just suffixed 
345 on the end of the element and separated by hyphens or underscores. For instance, 
346 all of the following are valid telephone number entries for uf_hcard_meta:
347
348     Tel: 02 8669 0001
349     Phone: 8669 0002
350     Fax: +612 8669 0003
351     Cell: 02 8669 0004
352     Mobile: 0401 8669 0005
353     Tel-Work: 123 456 7890
354     Phone-Work-Direct-Pref-Msg: +1 232 868 7123
355     Telephone-Home: 02 8669 0006
356
357 =head2 Config Elements
358
359 uf_hcard_meta also supports a couple of config elements that can be used to
360 override plugin config data on a per-story basis:
361
362 =over 4
363
364 =item HCard-Class (metamail) / hcard_class (meta)
365
366 This class (or list of classes) is appended to the class list applied to the
367 top-level hcard element in the rendered hcard i.e. it overrides the 
368 'class' config variable. 
369
370 =item HCard-Autoappend (metamail) / hcard_autoappend (meta)
371
372 This is a flag (0 or 1) indicating whether the rendered hcard should be 
373 automatically appended to the story body. It overrides the 'auto_append_to_body'
374 config variable.
375
376 =item HCard-Style (metamail) / hcard_style (meta)
377
378 One of the following styles: 'div-span', 'ul', 'dl', used to render the
379 hcard. It overrides the 'style' config variable.
380
381 =back
382
383 =head1 EXAMPLES
384
385 An simple example hcard for me:
386
387     Name:     Gavin Carr
388     Org:      Open Fusion
389     Role:     Chief Geek
390     Email:    gavin@openfusion.com.au
391     URL:      http://www.openfusion.net/
392     POBox:    PO Box 1222
393     Suburb:   Wahroonga
394     State:    NSW
395     Postcode: 2076
396     Country:  Australia
397
398 =head1 USAGE
399
400 uf_hcard_meta should be loaded after the meta plugins (meta
401 itself, or the metaclear/metamail/metadir/metafile family).
402
403 =head1 SEE ALSO
404
405 Microformats.org: http://www.microformats.org/, http://microformats.org/wiki/hcard.
406
407 Blosxom: http://blosxom.sourceforge.net/
408
409 =head1 BUGS
410
411 Probably, since hcards can be pretty horrendously complicated - please 
412 report to the author. Also, this plugin is pretty alpha - I'm not sure 
413 of quite a few of the interface elements, and would welcome input on how
414 attributes etc. should be represented. 
415
416 I also make no guarantees about backwards compatibility - future releases
417 may break existing hcards, so use at your own risk.
418
419 =head1 AUTHOR
420
421 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
422
423 =head1 LICENSE
424
425 Copyright 2007, Gavin Carr.
426
427 This plugin is licensed under the same terms as blosxom itself i.e.
428
429 Permission is hereby granted, free of charge, to any person obtaining a
430 copy of this software and associated documentation files (the "Software"),
431 to deal in the Software without restriction, including without limitation
432 the rights to use, copy, modify, merge, publish, distribute, sublicense,
433 and/or sell copies of the Software, and to permit persons to whom the
434 Software is furnished to do so, subject to the following conditions:
435
436 The above copyright notice and this permission notice shall be included
437 in all copies or substantial portions of the Software.
438
439 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
440 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
441 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
442 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
443 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
444 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
445 OTHER DEALINGS IN THE SOFTWARE.
446
447 =cut
448
449 # vim:ft=perl:sw=4