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