Add initial microformat plugins.
authorGavin Carr <gonzai@users.sourceforge.net>
Sun, 2 Dec 2007 22:19:26 +0000 (22:19 +0000)
committerGavin Carr <gonzai@users.sourceforge.net>
Sun, 2 Dec 2007 22:19:26 +0000 (22:19 +0000)
gavinc/hcard [new file with mode: 0644]
gavinc/uf_adr_meta [new file with mode: 0644]
gavinc/uf_geo_meta [new file with mode: 0644]
gavinc/uf_hcalendar_meta [new file with mode: 0644]
gavinc/uf_hcard_meta [new file with mode: 0644]
gavinc/uf_xfolk_meta [new file with mode: 0644]

diff --git a/gavinc/hcard b/gavinc/hcard
new file mode 100644 (file)
index 0000000..b02dfc1
--- /dev/null
@@ -0,0 +1,143 @@
+# Blosxom Plugin: hcard
+# Author(s): Gavin Carr <gavin@openfusion.com.au>
+# Version: 0.001000
+# Documentation: 'perldoc hcard'
+# Requires: metaclear, metamail, uf_hcard_meta
+
+package hcard;
+
+use strict;
+use IO::File;
+
+# Uncomment next line to enable debug output (don't uncomment debug() lines)
+#use Blosxom::Debug debug_level => 1;
+
+# --- Configuration defaults -----
+
+my %config = (
+
+  # Where is the hcard metadata?
+  hcard_meta_file => "$blosxom::datadir/hcard.yml",
+
+);
+
+# --------------------------------
+
+use vars qw($hcard);
+$hcard = '';
+
+sub start {
+    # Check $hcard_meta_file exists
+    unless (-f $config{hcard_meta_file}) {
+        warn "(hcard) cannot find hcard_meta_file file '$config{hcard_meta_file}' - aborting\n";
+        return 0;
+    }
+    return 1;
+}
+
+sub skip {
+    my $hcard_fh = IO::File->new( $config{hcard_meta_file}, 'r' )
+        or warn "(hcard) cannot open hcard_meta_file file '$config{hcard_meta_file}': $! - aborting\n"
+            and return 0;
+    my @hcard_data = <$hcard_fh>;
+    my $hcard_title = $hcard_data[0];
+    chomp $hcard_title;
+    my $hcard_body = join '', @hcard_data[ 1 .. $#hcard_data ];
+    $hcard_fh->close;
+    # debug(1, "hcard_body: $hcard_body");
+
+    unless ($hcard_body) {
+        warn "(hcard) no data found in hcard_meta_file file '$config{hcard_meta_file}' - aborting\n";
+        return 0;
+    }
+  
+    # Fake story calls to metaclear, metamail and uf_hcard_meta to render the hcard
+    my @story_args = ( undef, undef, undef, undef, \$hcard_title, \$hcard_body );
+    metaclear::story( @story_args );
+    metamail::story( @story_args );
+    uf_hcard_meta::story( @story_args );
+
+    $hcard = $uf_hcard_meta::hcard;
+
+    return 0;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+hcard - blosxom plugin to set a global $hcard::hcard variable for use in templates
+
+=head1 DESCRIPTION
+
+L<hcard> is a blosxom plugin to set a global $hcard::hcard variable for use in 
+templates. It is intended to allow you to set up a global hcard for yourself
+to be displayed somewhere in your blog template.
+
+To use, simply define the set of hcard data you want to use in the 'hcard_meta_file'
+file ($blosxom::datadir/hcard.yml, by default).
+
+=head1 EXAMPLES
+
+Here's an example hcard.yml for me:
+
+    Name: Gavin Carr
+    Organisation: Open Fusion
+    Role: Chief Geek
+    Email: gavin@openfusion.com.au
+    URL: http://www.openfusion.net/
+    Suburb: Wahroonga
+    State: NSW
+    Postcode: 2076
+    Country: Australia
+    Latitude: -33.717718
+    Longitude: 151.117158
+    HCard-Class: nodisplay
+    HCard-Style: div-span
+
+=head1 USAGE
+
+L<hcard> requires the L<uf_hcard_meta>, L<metaclear>, and L<metamail> plugins, 
+but has no particular ordering requirements with respect to them.
+
+=head1 SEE ALSO
+
+L<uf_hcard_meta>, L<metaclear>, L<metamail>.
+
+Microformats.org: http://www.microformats.org/, http://microformats.org/wiki/hcard.
+
+Blosxom: http://blosxom.sourceforge.net/
+
+=head1 AUTHOR
+
+Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
+
+=head1 LICENSE
+
+Copyright 2007, Gavin Carr.
+
+This plugin is licensed under the same terms as blosxom itself i.e.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+=cut
+
+# vim:ft=perl:sw=4
diff --git a/gavinc/uf_adr_meta b/gavinc/uf_adr_meta
new file mode 100644 (file)
index 0000000..dd9f18d
--- /dev/null
@@ -0,0 +1,218 @@
+# Blosxom Plugin: uf_adr_meta
+# Author(s): Gavin Carr <gavin@openfusion.com.au>
+# Version: 0.001000
+# Documentation: 'perldoc uf_adr_meta'
+
+package uf_adr_meta;
+
+use strict;
+
+# Uncomment next line to enable debug output (don't uncomment debug() lines)
+#use Blosxom::Debug debug_level => 1;
+
+# --- Configuration defaults -----
+
+my %config = (
+
+  # Extra CSS classes to add to the microformat container e.g. to turn display off
+  class => '',
+  #class => 'nodisplay',
+
+  # Whether to automatically add microformat to story bodies. If not set, 
+  # you must explicitly add $uf_adr_meta::adr to a template somewhere.
+  auto_append_to_body => 1,
+
+  # What markup style to use for your adr, if auto-appending. 
+  # 3 styles are currently defined: 
+  # 'div-span' uses a 'div' elt for the container, and 'span' elements for the fields
+  # 'ul' uses a 'ul' list for the container, and 'li' elements for the fields
+  # 'dl' uses a 'dl' list for the container, 'dt' elements for field names, and 
+  #    'dd' elements for the fields themselves
+  #style => 'div-span',
+  #style => 'ul',
+  style => 'dl',
+
+);
+
+# --------------------------------
+
+use vars qw($adr);
+
+# Official adr attributes
+my @attr = qw(post-office-box extended-address street-address
+              locality region postal-code country-name);
+# Attribute aliases
+my %alias = (
+    'post-office-box'       => 'pobox',
+    'street-address'        => 'street',
+    locality                => 'city',
+    region                  => 'state',
+    'postal-code'           => 'postcode',
+    'country-name'          => 'country',
+);
+# Attributes which if set will cause us to skip this plugin (looks like an hcard)
+my @skip_attr = qw(fn name);
+
+$config{style} = 'div-span' unless $config{style} eq 'ul' or $config{style} eq 'dl';
+
+sub start { 1 }
+
+# Return the first existing metadata item key and value given a list of keys
+sub _get_meta {
+    for my $attr ( @_ ) {
+        my $meta_attr = $attr;
+        $meta_attr =~ s/-/_/g;
+        my $value = $blosxom::meta{$meta_attr};
+        $value = eval "\$meta::$attr" unless defined $value;
+        return wantarray ? ( $attr, $value ) : $value if defined $value;
+    }
+    return wantarray ? () : undef;
+}
+
+sub story {
+    my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
+
+    # Skip if any of the @skip_attr are set
+    for (@skip_attr) {
+        return 1 if $blosxom::meta{$_} || eval "\$meta::$_"; 
+    }
+
+    my $story_style = _get_meta( 'adr_style' ) || $config{style};
+    my $ctag = $story_style eq 'div-span' ? 'div' : $story_style;
+    my $etag = $story_style eq 'div-span' ? 'span' :
+               $story_style eq 'ul' ? 'li' : 'dd';
+
+    $adr = '';
+    for my $attr ( @attr ) {
+        my $meta_attr = $attr;
+        $meta_attr =~ s/-/_/g;
+        my $value = $blosxom::meta{$meta_attr} || eval "\$meta::$attr" 
+                 || $blosxom::meta{ $alias{$attr} } || eval "\$meta::$alias{$attr}";
+        next unless defined $value;
+        $adr .= qq(<dt>$attr</dt>) if $story_style eq 'dl';
+        $adr .= qq(<$etag class="$attr">$value</$etag>\n);
+    }
+    if ($adr) {
+      my $container_classes = 'adr';
+      if (my $meta_class = _get_meta('adr_class')) {
+        $container_classes .= " $meta_class";
+      }
+      else {
+        $container_classes .= " $config{class}" if $config{class};
+      }
+      $adr = qq(<$ctag class="$container_classes">\n$adr</$ctag>\n);
+      # debug(1, "uf_adr_meta: $adr\n");
+    }
+
+    my $autoappend = _get_meta( 'adr_autoappend' );
+    $autoappend = $config{auto_append_to_body} unless defined $autoappend;
+    return 1 unless $autoappend;
+
+    $$body_ref .= "\n\n$adr\n\n";
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+uf_adr_meta - plugin to create an 'adr' microformat tag from post 
+metadata
+
+=head1 DESCRIPTION
+
+uf_adr_meta is a plugin to create an 'adr' microformat tag from metadata 
+in your post. The microformat tag is created in the $uf_adr_meta::adr 
+variable for use in templates or by other plugins, or if the 
+'auto_append_to_body' config variable is set (it is by default), 
+uf_adr_meta will append the tag to your story body automatically.
+
+=head2 OPTIONAL METADATA ITEMS
+
+=over 4
+
+=item post-office-box (alt: pobox)
+
+=item extended-address 
+
+=item street-address (alt: street)
+
+=item locality (alt: city)
+
+=item region (alt: state)
+
+=item country-name (alt: country)
+
+=back
+
+=head2 Config Elements
+
+uf_adr_meta also supports a couple of config elements that can be used to
+override plugin config data on a per-story basis:
+
+=over 4
+
+=item Adr-Class (metamail) / adr_class (meta)
+
+This class (or list of classes) is appended to the class list applied to the
+top-level adr element in the rendered adr i.e. it overrides the 
+'class' config variable. 
+
+=item Adr-Autoappend (metamail) / adr_autoappend (meta)
+
+This is a flag (0 or 1) indicating whether the rendered adr should be 
+automatically appended to the story body. It overrides the 'auto_append_to_body'
+config variable.
+
+=item Adr-Style (metamail) / adr_style (meta)
+
+One of the following styles: 'div-span', 'ul', 'dl', used to render the adr. 
+It overrides the 'style' config variable.
+
+=back
+
+=head1 USAGE
+
+uf_adr_meta should be loaded after the meta plugins (meta
+itself, or the metaclear/metamail/metadir/metafile family).
+
+=head1 SEE ALSO
+
+Microformats.org: http://www.microformats.org/, http://microformats.org/wiki/address.
+
+Blosxom: http://blosxom.sourceforge.net/
+
+=head1 AUTHOR
+
+Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
+
+=head1 LICENSE
+
+Copyright 2007, Gavin Carr.
+
+This plugin is licensed under the same terms as blosxom itself i.e.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+=cut
+
+# vim:ft=perl
diff --git a/gavinc/uf_geo_meta b/gavinc/uf_geo_meta
new file mode 100644 (file)
index 0000000..fbb06c7
--- /dev/null
@@ -0,0 +1,221 @@
+# Blosxom Plugin: uf_geo_meta
+# Author(s): Gavin Carr <gavin@openfusion.com.au>
+# Version: 0.001000
+# Documentation: 'perldoc uf_geo_meta'
+
+package uf_geo_meta;
+
+use strict;
+
+# Uncomment next line to enable debug output (don't uncomment debug() lines)
+#use Blosxom::Debug debug_level => 1;
+
+# --- Configurable variables -----
+
+my %config = (
+
+  # Extra CSS classes to add to the microformat container e.g. to turn display off
+  class => '',
+  #class => 'nodisplay',
+
+  # Whether to automatically add microformat to story bodies. If not set, 
+  # you must explicitly add $uf_adr_meta::adr to a template somewhere.
+  auto_append_to_body => 1,
+
+  # What markup style to use for your adr, if auto-appending. 
+  # 3 styles are currently defined: 
+  # 'div-span' uses a 'div' elt for the container, and 'span' elements for the fields
+  # 'ul' uses a 'ul' list for the container, and 'li' elements for the fields
+  # 'dl' uses a 'dl' list for the container, 'dt' elements for field names, and 
+  #    'dd' elements for the fields themselves
+  #style => 'div-span',
+  #style => 'ul',
+  style => 'dl',
+
+);
+
+# --------------------------------
+
+use vars qw($geo);
+
+# Attributes which if set will cause us to skip this plugin (looks like an adr)
+my @skip_attr = qw(locality region postal-code country-name city state postcode country);
+
+$config{style} = 'div-span' unless $config{style} eq 'ul' or $config{style} eq 'dl';
+
+sub start { 1 }
+
+# Return the first existing metadata item key and value given a list of keys
+sub _get_meta {
+    for my $attr ( @_ ) {
+        my $meta_attr = $attr;
+        $meta_attr =~ s/-/_/g;
+        my $value = $blosxom::meta{$meta_attr};
+        $value = eval "\$meta::$attr" unless defined $value;
+        return wantarray ? ( $attr, $value ) : $value if defined $value;
+    }
+    return wantarray ? () : undef;
+}
+
+sub story {
+    my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
+
+    # Skip if any of the @skip_attr are set
+    for (@skip_attr) {
+        return 1 if $blosxom::meta{$_} || eval "\$meta::$_"; 
+    }
+
+    my $story_style = _get_meta( 'geo_style' ) || $config{style};
+    my $ctag = $story_style eq 'div-span' ? 'div' : $story_style;
+    my $etag = $story_style eq 'div-span' ? 'span' :
+               $story_style eq 'ul' ? 'li' : 'dd';
+    my $sep = ', ' if $story_style eq 'div-span';
+
+    my $latitude = _get_meta('latitude');
+    my $longitude = _get_meta('longitude');
+    return 1 unless defined $latitude && defined $longitude;
+
+    $geo = '';
+    my $container_classes = 'geo';
+    if (my $meta_class = _get_meta('geo_class')) {
+      $container_classes .= " $meta_class";
+    }
+    else {
+      $container_classes .= " $config{class}" if $config{class};
+    }
+
+    $geo .= qq(<$ctag class="$container_classes">);
+    $geo .= qq(<dt>Latitude</dt>) if $story_style eq 'dl';
+    $geo .= qq(<$etag class="latitude">$latitude</$etag>);
+    $geo .= $sep if $story_style = 'div-span';
+    $geo .= qq(<dt>Longitude</dt>) if $story_style eq 'dl';
+    $geo .= qq(<$etag class="longitude">$longitude</$etag>);
+    $geo .= qq(</$ctag>);
+    # debug(1, "uf_geo_meta: $geo\n");
+
+    my $autoappend = _get_meta( 'geo_autoappend' );
+    $autoappend = $config{auto_append_to_body} unless defined $autoappend;
+    return 1 unless $autoappend;
+
+    $$body_ref .= "\n\n$geo\n\n";
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+uf_geo_meta - plugin to create a 'geo' microformat tag from post metadata
+
+=head1 DESCRIPTION
+
+uf_geo_meta is a plugin to create a 'geo' microformat tag from metadata
+in your post. The microformat tag is created in the $uf_geo_meta::geo
+variable for use in templates or by other plugins, or if the
+'auto_append_to_body' config variable is set (it is by default), uf_geo_meta 
+will append the tag to your story body automatically.
+
+=head2 REQUIRED METADATA ITEMS
+
+=over 4
+
+=item latitude
+
+A decimal between -90.0 (South Pole) and +90.0 (North Pole), indicating 
+degrees of latitude.
+
+=item longitude
+
+A decimal between -180.0 (western hemisphere) and +180.0 (eastern hemisphere), 
+indicating degrees of longitude.
+
+=back
+
+If any required metadata is missing the plugin just skips the story.
+
+=head2 Config Elements
+
+uf_geo_meta also supports a couple of config elements that can be used to
+override plugin config data on a per-story basis:
+
+=over 4
+
+=item Geo-Class (metamail) / geo_class (meta)
+
+This class (or list of classes) is appended to the class list applied to the
+top-level geo element in the rendered geo i.e. it overrides the 
+'class' config variable. 
+
+=item Geo-Autoappend (metamail) / geo_autoappend (meta)
+
+This is a flag (0 or 1) indicating whether the rendered geo should be 
+automatically appended to the story body. It overrides the 'auto_append_to_body'
+config variable.
+
+=item Geo-Style (metamail) / geo_style (meta)
+
+One of the following styles: 'div-span', 'ul', 'dl', used to render the geo. 
+It overrides the 'style' config variable.
+
+=back
+
+=head1 EXAMPLE
+
+Adding a geo microformat to  your post then becomes as simple as:
+
+  Testing uf_geo_meta
+  Latitude: -33.717770
+  Longitude: 151.115886
+
+if using metamail, or:
+
+  Random blog post
+  meta-latitude: -33.717770
+  meta-longitude: 151.115886
+
+=head1 USAGE
+
+uf_geo_meta should be loaded after the meta plugins (meta
+itself, or the metaclear/metamail/metadir/metafile family).
+
+=head1 SEE ALSO
+
+Microformats.org: http://www.microformats.org/, 
+http://microformats.org/wiki/geo.
+
+Blosxom: http://blosxom.sourceforge.net/
+
+=head1 AUTHOR
+
+Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
+
+=head1 LICENSE
+
+Copyright 2007, Gavin Carr.
+
+This plugin is licensed under the same terms as blosxom itself i.e.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+=cut
+
+# vim:ft=perl
diff --git a/gavinc/uf_hcalendar_meta b/gavinc/uf_hcalendar_meta
new file mode 100644 (file)
index 0000000..9cfa760
--- /dev/null
@@ -0,0 +1,353 @@
+# Blosxom Plugin: uf_hcalendar_meta
+# Author(s): Gavin Carr <gavin@openfusion.com.au>
+# Version: 0.001000
+# Documentation: 'perldoc uf_hcalendar_meta'
+
+package uf_hcalendar_meta;
+
+use strict;
+
+# Uncomment next line to enable debug output (don't uncomment debug() lines)
+#use Blosxom::Debug debug_level => 1;
+
+# --- Configurable variables -----
+
+my %config = (
+
+  # Extra CSS classes to add to the microformat container e.g. to turn display off
+  class => '',
+  #class => 'nodisplay',
+
+  # Whether to automatically add microformat to story bodies. If not set, 
+  # you must explicitly add $uf_adr_meta::adr to a template somewhere.
+  auto_append_to_body => 1,
+
+  # What markup style to use for your adr, if auto-appending. 
+  # 3 styles are currently defined: 
+  # 'div-span' uses a 'div' elt for the container, and 'span' elements for the fields
+  # 'ul' uses a 'ul' list for the container, and 'li' elements for the fields
+  # 'dl' uses a 'dl' list for the container, 'dt' elements for field names, and 
+  #    'dd' elements for the fields themselves
+  #style => 'div-span',
+  #style => 'ul',
+  style => 'dl',
+
+);
+
+# --------------------------------
+
+use vars qw($hcalendar);
+
+my @required = qw(summary dtstart);
+my @optional = qw(dtend duration description location url uid);
+my %label = (
+  dtstart   => 'DtStart',
+  dtend     => 'DtEnd',
+  uid       => 'UID',
+  url       => 'URL',
+);
+
+$config{style} = 'div-span' unless $config{style} eq 'ul' or $config{style} eq 'dl';
+
+sub start { 1 }
+
+# Return the first existing metadata item key and value given a list of keys
+sub _get_meta {
+    for my $attr ( @_ ) {
+        my $meta_attr = $attr;
+        $meta_attr =~ s/-/_/g;
+        my $value = $blosxom::meta{$meta_attr};
+        $value = eval "\$meta::$attr" unless defined $value;
+        return wantarray ? ( $attr, $value ) : $value if defined $value;
+    }
+    return wantarray ? () : undef;
+}
+
+sub _format_date {
+    my ($date) = @_;
+    my $iso_date = $date;
+    $iso_date =~ s/^(\d{4}-\d{2}-\d{2})(\s+)/$1T/;
+    return $iso_date;  
+}
+
+sub _format_duration {
+    my ($duration) = @_;
+    my $iso_duration = uc $duration;
+
+    # Trim
+    $iso_duration =~ s/^\s+//;
+    $iso_duration =~ s/\s+$//;
+
+    # If $iso_duration begins with an H, M, or S element, and no P, insert PT
+    $iso_duration =~ s/^(\d+)([HMS])/PT$1$2/;
+
+    # Otherwise, if $iso_duration begins without a P, insert one
+    $iso_duration =~ s/^(\d)/P$1/;
+
+    # Replace date-time whitespace with 'T'
+    $iso_duration =~ s/([PYMD])\s+/$1T/;
+
+    return $iso_duration;
+}
+
+sub story {
+    my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
+
+    my %meta = ();
+    for (@required, @optional) {
+      $meta{$_} = _get_meta($_);
+    }
+    my @req_count = map { $meta{$_} ? 1 : () } @required;
+    return 1 unless @req_count == @required;
+
+    my $story_style = _get_meta( 'hcal_style' ) || $config{style};
+    my $ctag = $story_style eq 'div-span' ? 'div' : $story_style;
+    my $etag = $story_style eq 'div-span' ? 'span' :
+               $story_style eq 'ul' ? 'li' : 'dd';
+
+    $hcalendar = '';
+    my $container_classes = 'vevent';
+    if (my $meta_class = _get_meta('hcal_class')) {
+      $container_classes .= " $meta_class";
+    }
+    else {
+      $container_classes .= " $config{class}" if $config{class};
+    }
+    $hcalendar .= qq(<$ctag class="$container_classes">\n);
+    for (@required, @optional) {
+        next unless defined $meta{$_};
+        $hcalendar .= sprintf qq(<dt>%s</dt>), $label{$_} || ucfirst $_ 
+            if $story_style eq 'dl';
+        if ($_ eq 'dtstart' || $_ eq 'dtend') {
+            my $iso_date = _format_date($meta{$_});
+            $hcalendar .= qq(<$etag><abbr class="$_" title="$iso_date">$meta{$_}</abbr></$etag>\n);
+        }
+        elsif ($_ eq 'duration') {
+            my $iso_duration = _format_duration($meta{$_});
+            $hcalendar .= qq(<$etag><abbr class="$_" title="$iso_duration">$meta{$_}</abbr></$etag>\n);
+        }
+        elsif ($_ eq 'url') {
+            $hcalendar .= qq(<$etag><a class="$_" href="$meta{url}">$meta{url}</a></$etag>\n);
+        }
+        else {
+            $hcalendar .= qq(<$etag class="$_">$meta{$_}</$etag>\n);
+        }
+    }
+    $hcalendar .= qq(</$ctag>\n);
+    # debug(1, "uf_hcalendar_meta: $hcalendar\n");
+
+    my $autoappend = _get_meta( 'hcal_autoappend' );
+    $autoappend = $config{auto_append_to_body} unless defined $autoappend;
+    return 1 unless $autoappend;
+
+    $$body_ref .= "\n\n$hcalendar\n\n";
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+uf_hcalendar_meta - plugin to create an 'hcalendar' microformat tag from 
+post metadata
+
+=head1 DESCRIPTION
+
+uf_hcalendar_meta is a plugin to create an 'hcalendar' microformat tag 
+from metadata in your post. The microformat tag is created in the 
+$uf_hcalendar_meta::hcalendar variable for use in templates or by other 
+plugins, or if the $auto_append_to_body flag is set (it is by default), 
+uf_hcalendar_meta will append the tag to your story body automatically.
+
+=head2 REQUIRED METADATA ITEMS
+
+(If using the 'metamail/metadir/metafile' plugins, metadata items
+are matched case insensitively.)
+
+=over 4
+
+=item summary
+
+The summary or title of the event.
+
+=item dtstart
+
+The start date/time of the event. 
+
+Must be given as an ISO 8601 calendar date, in the form 
+YYYY-MM-DDTHH:MM:SS or YYYYMMDDTHHMMSS, with an optional trailing 
+timezone of the form /[+-]HH(:?MM)?/. Any number of rightmost time 
+elements may be omitted. Hours must be given in 24-hour time.
+
+For convenience, this plugin allows the 'T' marker to be replaced by 
+whitespace.
+
+The following are all valid dtstart values, for example:
+
+=over 4
+
+=item 2007-09-01T19:30:00+10:00
+
+=item 20070901T193000-10
+
+=item 2007-09-01
+
+=item 2007-09-01 17:45
+
+=back
+
+=back
+
+And one of:
+
+=over 4
+
+=item dtend
+
+The end date/time of the event. Must be an ISO 8601 calendar date 
+as defined for dtstart above.
+
+=item duration
+
+The duration of the event. This may be an ISO 8601 duration, of the
+form PnnYnnMnnDTnnHnnMnnS e.g. "P3Y6M4DT12H30M0S". Elements may be
+omitted if their duration is zero. The smallest value used may also 
+have a decimal fraction, as in "P0.5Y" to indicate half a year.
+
+For convenience, this plugin interprets the units case-insensitively,
+allows the 'P' and 'T' markers to be omitted, and also accepts 
+whitespace in the place of the 'T' time marker. 
+
+Note that because months and minutes use the same signifier, there is
+ambiguity about the meaning of 'P1M' and '1M'. This plugin interprets
+'M' values as follows:
+
+=over 4
+
+=item 1M
+
+Interpreted as minutes, since this is the most common use case, and 
+since ISO 8601 really requires a leading 'P' signifier.
+
+=item P1M
+
+Interpreted as months, following ISO 8601.
+
+=item PT1M
+
+Interpreted as minutes, following ISO 8601.
+
+=back
+
+=back
+
+
+=head2 OPTIONAL METADATA ITEMS
+
+=over 4
+
+=item description
+
+A description of the event, sometimes longer than the summary.
+
+=item dtend
+
+The end date/time of the event, as discussed above.
+
+=item duration
+
+The duration of the event, as discussed above.
+
+=item location
+
+A string describing the location of the event.
+
+=item url
+
+A canonical URL for the event.
+
+=item uid
+
+A unique identifier for this event. Apparently required by some
+versions of Microsoft Outlook.
+
+=back
+
+=head2 Config Elements
+
+uf_hcalendar_meta also supports a couple of config elements that can be used to
+override plugin config data on a per-story basis:
+
+=over 4
+
+=item HCal-Class (metamail) / hcal_class (meta)
+
+This class (or list of classes) is appended to the class list applied to the
+top-level hcalendar element in the rendered hcalendar i.e. it overrides the 
+'class' config variable. 
+
+=item HCal-Autoappend (metamail) / hcal_autoappend (meta)
+
+This is a flag (0 or 1) indicating whether the rendered hcalendar should be 
+automatically appended to the story body. It overrides the 'auto_append_to_body'
+config variable.
+
+=item HCal-Style (metamail) / hcal_style (meta)
+
+One of the following styles: 'div-span', 'ul', 'dl', used to render the hcalendar. 
+It overrides the 'style' config variable.
+
+=back
+
+=head1 USAGE
+
+uf_hcalendar_meta should be loaded after the meta plugins (meta
+itself, or the metaclear/metamail/metadir/metafile family).
+
+=head1 SEE ALSO
+
+Microformats.org: http://www.microformats.org/,
+http://microformats.org/wiki/hcalendar.
+
+Blosxom: http://blosxom.sourceforge.net/
+
+=head1 BUGS
+
+Only the most common hcalendar attributes have been implemented
+so far. Please let me know if you'd like something not available 
+yet.
+
+=head1 AUTHOR
+
+Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
+
+=head1 LICENSE
+
+Copyright 2007, Gavin Carr.
+
+This plugin is licensed under the same terms as blosxom itself i.e.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+=cut
+
+# vim:ft=perl
diff --git a/gavinc/uf_hcard_meta b/gavinc/uf_hcard_meta
new file mode 100644 (file)
index 0000000..55efc3b
--- /dev/null
@@ -0,0 +1,449 @@
+# Blosxom Plugin: uf_hcard_meta
+# Author(s): Gavin Carr <gavin@openfusion.com.au>
+# Version: 0.001000
+# Documentation: 'perldoc uf_hcard_meta'
+
+package uf_hcard_meta;
+
+use strict;
+
+# Uncomment next line to enable debug output (don't uncomment debug() lines)
+#use Blosxom::Debug debug_level => 2;
+
+# --- Configuration defaults -----
+
+my %config = (
+
+  # Extra CSS classes to add to the microformat container e.g. to turn display off
+  class => '',
+  #class => 'nodisplay',
+
+  # Whether to automatically add microformat to story bodies. If not set, 
+  # you must explicitly add $uf_adr_meta::adr to a template somewhere.
+  auto_append_to_body => 1,
+
+  # What markup style to use for your adr, if auto-appending. 
+  # 3 styles are currently defined: 
+  # 'div-span' uses a 'div' elt for the container, and 'span' elements for the fields
+  # 'ul' uses a 'ul' list for the container, and 'li' elements for the fields
+  # 'dl' uses a 'dl' list for the container, 'dt' elements for field names, and 
+  #    'dd' elements for the fields themselves
+  #style => 'div-span',
+  #style => 'ul',
+  style => 'dl',
+
+);
+
+# --------------------------------
+
+use vars qw($hcard);
+
+# Official hcard attributes
+my @req_attr = qw(fn);
+my %opt_attr = (
+    'adr'         => [ qw(address-type post-office-box extended-address street-address
+                          locality region postal-code country-name) ],
+    'agent'       => 1,
+    'bday'        => 1,
+    'email'       => 1,
+    'geo'         => [ qw(latitude longitude) ],
+    'key'         => 1,
+    'label'       => 1,
+    'logo'        => 1,
+    'mailer'      => 1,
+    'n'           => [ qw(honorific-prefix given-name additional-name family-name honorific-suffix) ],
+    'nickname'    => 1,
+    'note'        => 1,
+    'org'         => [ qw(organization-name organization-unit) ],
+    'photo'       => 1,
+    'rev'         => 1,
+    'role'        => 1,
+    'sort-string' => 1,
+    'sound'       => 1,
+    'tel'         => 1,
+    'tz'          => 1,
+    'uid'         => 1,
+    'url'         => 1,
+);
+  
+# Attribute aliases
+my %alias = (
+    'fn'                    => [ 'name' ],
+    'email-value'           => [ 'email' ],
+    'tel'                   => [ qw(telephone phone) ],
+    'organization-name'     => [ qw(org organisation organization) ],
+    'post-office-box'       => [ 'pobox' ],
+    'street-address'        => [ 'street' ],
+    'locality'              => [ 'suburb', 'city' ],
+    'region'                => [ 'state' ],
+    'postal-code'           => [ 'postcode' ],
+    'country-name'          => [ 'country' ],
+);
+my %attr_types = map { $_ => 1 } qw(tel);
+my %attr_types_standalone = (
+    tel                     => { map { $_ => 1 } qw(fax cell mobile) },
+);
+my %alias_types = (
+    mobile                  => 'cell',
+);
+
+$config{style} = 'div-span' unless $config{style} eq 'ul' or $config{style} eq 'dl';
+
+sub start { 1 }
+
+# Return the first existing metadata item key and value given a list of keys
+sub _get_meta {
+    for my $attr ( @_ ) {
+        my $meta_attr = $attr;
+        $meta_attr =~ s/-/_/g;
+        my $value = $blosxom::meta{$meta_attr};
+        $value = eval "\$meta::$attr" unless defined $value;
+        return wantarray ? ( $attr, $value ) : $value if defined $value;
+    }
+    return wantarray ? () : undef;
+}
+
+sub _add_attr {
+    my ($hcard, $attr, $value, $style, $parent_attr, $parent_started, $type) = @_;
+
+    # Start parent if set and not started
+    if ($parent_attr && (! defined $parent_started || ! $$parent_started)) {
+        $$hcard .= qq(<span class="$parent_attr">\n);
+        $$parent_started = 1 if defined $parent_started;
+    }
+
+    # Append hcard output
+    my $label = '';
+    if ($type) {
+        $label = join ', ', map { qq(<span class="type">$_</span>) } split /[_\W]+/, $type;
+    }
+    elsif ($attr =~ m/^(\w+)-(value)$/) {
+        $label = $1;
+        $attr = $2;
+    } 
+    elsif ($style eq 'dl') {
+        $label = $attr;
+        $label =~ s/\s+/-/g;
+    }
+    my $type_string;
+    if ($style eq 'dl') {
+        $$hcard .= qq(<dt>$label</dt>);
+        $type_string = '';
+    }
+    elsif ($label) {
+        $type_string = "($label) ";
+    }
+    my $etag = $style eq 'div-span' ? 'span' : 
+               $style eq 'ul' ? 'li' : 'dd';
+    $$hcard .= qq(<$etag class="$attr">$type_string$value</$etag>\n);
+
+    # Close parent unless we have a $parent_started flag to set
+    $$hcard .= qq(</span>\n) if $parent_attr && ! defined $parent_started;
+}
+
+sub story {
+    my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
+
+    # Skip unless all required attributes are set
+    for (@req_attr) {
+        my $value = _get_meta($_, $alias{$_} ? @{$alias{$_}} : ());
+        unless ($value) {
+            # debug(2, "No name attribute found in $path/$filename - skipping post");
+            return 1;
+        }
+    }
+
+    my $story_style = _get_meta( 'hcard_style' ) || $config{style};
+    my $ctag = $story_style eq 'div-span' ? 'div' : $story_style;
+
+    $hcard = '';
+    my $container_classes = 'vcard';
+    if (my $meta_class = _get_meta('hcard_class')) {
+      $container_classes .= " $meta_class";
+    }
+    else {
+      $container_classes .= " $config{class}" if $config{class};
+    }
+    $hcard .= qq(<$ctag class="$container_classes">\n);
+    for my $attr ( @req_attr ) {
+        my $value = _get_meta( $attr, $alias{$attr} ? @{$alias{$attr}} : () );
+        _add_attr(\$hcard, $attr, $value, $story_style) if $value;
+    }
+    for my $attr ( sort keys %opt_attr ) {
+        # Allow nested attributes
+        if (ref $opt_attr{$attr}) {
+            my $parent_attr = $attr;
+            my $parent_started = 0;
+            for $attr ( @{ $opt_attr{$parent_attr} } ) {
+                my $value = _get_meta( $attr, $alias{$attr} ? @{$alias{$attr}} : () );
+                _add_attr(\$hcard, $attr, $value, $story_style, $parent_attr, \$parent_started)
+                    if $value;
+            }
+            if ($parent_started) {
+                $hcard .= qq(</span>\n);
+                next;
+            }
+        }
+    
+        # Allow bare attributes and aliases
+        my $value = _get_meta( $attr, $alias{$attr} ? @{$alias{$attr}} : () );
+        _add_attr(\$hcard, $attr, $value, $story_style) if $value;
+
+        # Allow typed attributes 
+        if ($attr_types{ $attr }) {
+            # TODO: need to support $meta package variables here too
+            for my $meta (sort keys %blosxom::meta) {
+                # Search for all metadata beginning with $attr or aliases
+                for my $a ( $attr, $alias{$attr} ? @{$alias{$attr}} : () ) {
+                    if (lc $meta =~ m/^$a[^a-z]+(\w+)$/) {
+                        _add_attr(\$hcard, 'value', $blosxom::meta{ $meta }, $story_style, $attr, undef, $1);
+                    }
+                }
+            }
+
+            # Allow standalone types
+            for my $type ( sort keys %{ $attr_types_standalone{ $attr } } ) {
+                if (my $value = _get_meta( $type )) {
+                    _add_attr(\$hcard, 'value', $value, $story_style, $attr, undef, 
+                              $alias_types{ $type } || $type);
+                }
+
+            }
+        }
+    }
+    $hcard .= qq(</$ctag>\n);
+    # debug(2, "hcard $hcard\n");
+
+    my $autoappend = _get_meta( 'hcard_autoappend' );
+    $autoappend = $config{auto_append_to_body} unless defined $autoappend;
+    return 1 unless $autoappend;
+
+    $$body_ref .= "\n\n$hcard\n\n";
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+uf_hcard_meta - plugin to create an 'hcard' microformat tag from post 
+metadata
+
+=head1 DESCRIPTION
+
+uf_hcard_meta is a plugin to create an 'hcard' microformat tag from 
+metadata in your post. The microformat tag is created in the 
+$uf_hcard_meta::hcard story variable for use in templates or by other 
+plugins, or if the 'auto_append_to_body' config variable is set (it is 
+by default), uf_hcard_meta will append the tag to your story body 
+automatically.
+
+The following metadata items are supported. By and large the official
+hcard attribute name is supported, and sometimes one or aliases 
+(labelled alt below) may also be supported. See the hcard definition at
+http://www.microformats.org/wiki/hcard for definitions and discussion
+of attributes and usage.
+
+=head2 REQUIRED METADATA ITEMS
+
+=over 4
+
+=item fn (alt: name) - full name
+
+=back
+
+=head2 OPTIONAL METADATA ITEMS
+
+=over 4
+
+=item address-type
+
+=item post-office-box (alt: pobox)
+
+=item extended-address 
+
+=item street-address (alt: street)
+
+=item locality (alt: suburb, city)
+
+=item region (alt: state)
+
+=item postal-code (alt: postcode)
+
+=item country-name (alt: country)
+
+=item agent
+
+=item bday
+
+=item cell (alt: mobile)
+
+=item email
+
+=item fax
+
+=item key
+
+=item label
+
+=item latitude
+
+=item longitude
+
+=item logo
+
+=item mailer
+
+=item honorific-prefix
+
+=item given-name
+
+=item additional-name
+
+=item family-name
+
+=item honorific-suffix
+
+=item nickname
+
+=item note
+
+=item organization-name (alt: org, organization, organisation)
+
+=item organization-unit
+
+=item photo
+
+=item rev
+
+=item role
+
+=item sort-string
+
+=item sound
+
+=item tel (alt: telephone, phone)
+
+And see also the Telephone Element Handling section below.
+
+=item tz
+
+=item uid
+
+=item uri
+
+=back
+
+=head2 Telephone Element Handling
+
+hcard telephone numbers may have type attributes, and because most people have
+multiple telephone numbers, uf_hcard_meta also supports decorating the 'tel' 
+element (or its aliases) with one or more hcard telephone types, just suffixed 
+on the end of the element and separated by hyphens or underscores. For instance, 
+all of the following are valid telephone number entries for uf_hcard_meta:
+
+    Tel: 02 8669 0001
+    Phone: 8669 0002
+    Fax: +612 8669 0003
+    Cell: 02 8669 0004
+    Mobile: 0401 8669 0005
+    Tel-Work: 123 456 7890
+    Phone-Work-Direct-Pref-Msg: +1 232 868 7123
+    Telephone-Home: 02 8669 0006
+
+=head2 Config Elements
+
+uf_hcard_meta also supports a couple of config elements that can be used to
+override plugin config data on a per-story basis:
+
+=over 4
+
+=item HCard-Class (metamail) / hcard_class (meta)
+
+This class (or list of classes) is appended to the class list applied to the
+top-level hcard element in the rendered hcard i.e. it overrides the 
+'class' config variable. 
+
+=item HCard-Autoappend (metamail) / hcard_autoappend (meta)
+
+This is a flag (0 or 1) indicating whether the rendered hcard should be 
+automatically appended to the story body. It overrides the 'auto_append_to_body'
+config variable.
+
+=item HCard-Style (metamail) / hcard_style (meta)
+
+One of the following styles: 'div-span', 'ul', 'dl', used to render the
+hcard. It overrides the 'style' config variable.
+
+=back
+
+=head1 EXAMPLES
+
+An simple example hcard for me:
+
+    Name:     Gavin Carr
+    Org:      Open Fusion
+    Role:     Chief Geek
+    Email:    gavin@openfusion.com.au
+    URL:      http://www.openfusion.net/
+    POBox:    PO Box 1222
+    Suburb:   Wahroonga
+    State:    NSW
+    Postcode: 2076
+    Country:  Australia
+
+=head1 USAGE
+
+uf_hcard_meta should be loaded after the meta plugins (meta
+itself, or the metaclear/metamail/metadir/metafile family).
+
+=head1 SEE ALSO
+
+Microformats.org: http://www.microformats.org/, http://microformats.org/wiki/hcard.
+
+Blosxom: http://blosxom.sourceforge.net/
+
+=head1 BUGS
+
+Probably, since hcards can be pretty horrendously complicated - please 
+report to the author. Also, this plugin is pretty alpha - I'm not sure 
+of quite a few of the interface elements, and would welcome input on how
+attributes etc. should be represented. 
+
+I also make no guarantees about backwards compatibility - future releases
+may break existing hcards, so use at your own risk.
+
+=head1 AUTHOR
+
+Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
+
+=head1 LICENSE
+
+Copyright 2007, Gavin Carr.
+
+This plugin is licensed under the same terms as blosxom itself i.e.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+=cut
+
+# vim:ft=perl:sw=4
diff --git a/gavinc/uf_xfolk_meta b/gavinc/uf_xfolk_meta
new file mode 100644 (file)
index 0000000..4f310e5
--- /dev/null
@@ -0,0 +1,234 @@
+# Blosxom Plugin: uf_xfolk_meta
+# Author(s): Gavin Carr <gavin@openfusion.com.au>
+# Version: 0.001000
+# Documentation: 'perldoc uf_xfolk_meta'
+
+package uf_xfolk_meta;
+
+use strict;
+
+# Uncomment next line to enable debug output (don't uncomment debug() lines)
+#use Blosxom::Debug debug_level => 1;
+
+# --- Configurable variables -----
+
+my %config = (
+
+  # URL to use as base URL for tag links
+  tagbase => "$blosxom::url/tags",
+
+  # Extra CSS classes to add to the microformat container e.g. to turn display off
+  class => '',
+  #class => 'nodisplay',
+
+  # Whether to automatically add microformat to story bodies. If not set, 
+  # you must explicitly add $uf_adr_meta::adr to a template somewhere.
+  auto_append_to_body => 1,
+
+  # What markup style to use for your adr, if auto-appending. 
+  # 3 styles are currently defined: 
+  # 'div-span' uses a 'div' elt for the container, and 'span' elements for the fields
+  # 'ul' uses a 'ul' list for the container, and 'li' elements for the fields
+  # 'dl' uses a 'dl' list for the container, 'dt' elements for field names, and 
+  #    'dd' elements for the fields themselves
+  #style => 'div-span',
+  #style => 'ul',
+  style => 'dl',
+
+);
+
+# --------------------------------
+
+use vars qw($xfolk);
+
+# Attributes which if set will cause us to skip this plugin (looks like an hcard)
+my @skip_attr = qw(fn name);
+
+$config{style} = 'div-span' unless $config{style} eq 'ul' or $config{style} eq 'dl';
+
+sub start { 1 }
+
+# Return the first existing metadata item key and value given a list of keys
+sub _get_meta {
+    for my $attr ( @_ ) {
+        my $meta_attr = $attr;
+        $meta_attr =~ s/-/_/g;
+        my $value = $blosxom::meta{$meta_attr};
+        $value = eval "\$meta::$attr" unless defined $value;
+        return wantarray ? ( $attr, $value ) : $value if defined $value;
+    }
+    return wantarray ? () : undef;
+}
+
+sub story {
+    my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
+
+    # Skip if any of the @skip_attr are set
+    for (@skip_attr) {
+        return 1 if $blosxom::meta{$_} || eval "\$meta::$_"; 
+    }
+
+    my $story_style = _get_meta( 'xfolk_style' ) || $config{style};
+    my $ctag = $story_style eq 'div-span' ? 'div' : $story_style;
+    my $etag = $story_style eq 'div-span' ? 'span' :
+               $story_style eq 'ul' ? 'li' : 'dd';
+
+    # Required metadata
+    my $url = _get_meta('url', 'taggedlink');
+    my $title = _get_meta('title');
+    my $tags = _get_meta('tags');
+    my $description = _get_meta('description');
+    return 1 unless defined $url && defined $title;
+
+    $xfolk = '';
+    my $container_classes = 'xfolkentry';
+    if (my $meta_class = _get_meta('xfolk_class')) {
+      $container_classes .= " $meta_class";
+    }
+    else {
+      $container_classes .= " $config{class}" if $config{class};
+    }
+    $xfolk .= qq(<$ctag class="$container_classes">\n);
+    $xfolk .= qq(<dt>URL</dt>) if $story_style eq 'dl';
+    $xfolk .= qq(<$etag class="xfolk_link"><a class="taggedlink" href="$url" title="$title">$title</a></$etag>\n);
+    if ($tags) {
+      $xfolk .= qq(<dt>Tags</dt>) if $story_style eq 'dl';
+      $xfolk .= qq(<$etag class="xfolk_tags">);
+      my @tags = ();
+      for (split /\s*,\s*/, $tags) {
+        push @tags, qq(<a rel="tag" href="$config{tagbase}/$_">$_</a>);
+      }
+      $xfolk .= join ', ', @tags if @tags;
+      $xfolk .= qq(</$etag>\n);
+    }
+    if ($description) {
+      $xfolk .= qq(<dt>Description</dt>) if $story_style eq 'dl';
+      $xfolk .= qq(<$etag class="description">$description</$etag>\n)
+    }
+    $xfolk .= qq(</$ctag>\n);
+    # debug(1, "uf_xfolk_meta: $xfolk\n");
+
+    my $autoappend = _get_meta( 'xfolk_autoappend' );
+    $autoappend = $config{auto_append_to_body} unless defined $autoappend;
+    return 1 unless $autoappend;
+
+    $$body_ref .= "\n\n$xfolk\n\n";
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+uf_xfolk_meta - plugin to create a 'xfolk' (bookmark) microformat tag 
+from post metadata
+
+=head1 DESCRIPTION
+
+uf_xfolk_meta is a plugin to create an 'xfolk' (bookmark) microformat tag 
+from metadata in your post. The microformat tag is created in the 
+$uf_xfolk_meta::xfolk variable for use in templates or by other plugins, 
+or if the 'auto_append_to_body' config variable is set (it is by default), 
+uf_xfolk_meta will append the tag to your story body automatically.
+
+=head2 REQUIRED METADATA ITEMS
+
+=over 4
+
+=item url (or taggedlink)
+
+The URL for the bookmarked page.
+
+=item title
+
+The title to use for the bookmarked page.
+
+=back
+
+If any required metadata is missing the plugin just skips the story.
+
+=head2 OPTIONAL METADATA ITEMS
+
+=over 4
+
+=item tags
+
+Comma-separated list of tags applying to the bookmarked page.
+
+=item description
+
+Description or summary of the bookmarked page.
+
+=back
+
+=head2 Config Elements
+
+uf_xfolk_meta also supports a couple of config elements that can be used to
+override plugin config data on a per-story basis:
+
+=over 4
+
+=item XFolk-Class (metamail) / xfolk_class (meta)
+
+This class (or list of classes) is appended to the class list applied to the
+top-level xfolk element in the rendered xfolk i.e. it overrides the 
+'class' config variable. 
+
+=item XFolk-Autoappend (metamail) / xfolk_autoappend (meta)
+
+This is a flag (0 or 1) indicating whether the rendered xfolk should be 
+automatically appended to the story body. It overrides the 'auto_append_to_body'
+config variable.
+
+=item XFolk-Style (metamail) / xfolk_style (meta)
+
+One of the following styles: 'div-span', 'ul', 'dl', used to render the xfolk. 
+It overrides the 'style' config variable.
+
+=back
+
+=head1 USAGE
+
+uf_xfolk_meta should be loaded after the meta plugins (meta
+itself, or the metaclear/metamail/metadir/metafile family).
+
+=head1 SEE ALSO
+
+Microformats.org: http://www.microformats.org/, http://microformats.org/wiki/xfolk.
+
+Blosxom: http://blosxom.sourceforge.net/
+
+=head1 AUTHOR
+
+Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
+
+=head1 LICENSE
+
+Copyright 2007, Gavin Carr.
+
+This plugin is licensed under the same terms as blosxom itself i.e.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+=cut
+
+# vim:ft=perl