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