Show
Ignore:
Timestamp:
02/01/09 00:06:59 (3 years ago)
Author:
dom
Message:

fix up pod and add pod testing (fixes #45)

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • wiki-toolkit/trunk/lib/Wiki/Toolkit/Feed/Atom.pm

    r432 r497  
    1313use Wiki::Toolkit::Feed::Listing; 
    1414@ISA = qw( Wiki::Toolkit::Feed::Listing ); 
    15  
    16 sub new { 
    17     my $class = shift; 
    18     my $self  = {}; 
    19     bless $self, $class; 
    20  
    21     my %args = @_; 
    22     my $wiki = $args{wiki}; 
    23  
    24     unless ($wiki && UNIVERSAL::isa($wiki, 'Wiki::Toolkit')) { 
    25         croak 'No Wiki::Toolkit object supplied'; 
    26     } 
    27    
    28     $self->{wiki} = $wiki; 
    29    
    30     # Mandatory arguments. 
    31     foreach my $arg (qw/site_name site_url make_node_url atom_link/) { 
    32         croak "No $arg supplied" unless $args{$arg}; 
    33         $self->{$arg} = $args{$arg}; 
    34     } 
    35  
    36     # Must-supply-one-of arguments 
    37     my %mustoneof = ( 'html_equiv_link' => ['html_equiv_link','recent_changes_link'] ); 
    38     $self->handle_supply_one_of(\%mustoneof,\%args); 
    39    
    40     # Optional arguments. 
    41     foreach my $arg (qw/site_description software_name software_version software_homepage encoding/) { 
    42         $self->{$arg} = $args{$arg} || ''; 
    43     } 
    44  
    45     # Supply some defaults, if a blank string isn't what we want 
    46     unless($self->{encoding}) { 
    47         $self->{encoding} = $self->{wiki}->store->{_charset}; 
    48     } 
    49  
    50     $self->{timestamp_fmt} = $Wiki::Toolkit::Store::Database::timestamp_fmt; 
    51     $self->{utc_offset} = strftime "%z", localtime; 
    52     $self->{utc_offset} =~ s/(..)(..)$/$1:$2/; 
    53    
    54     # Escape any &'s in the urls 
    55     foreach my $key qw(site_url atom_link) { 
    56         my @ands = ($self->{$key} =~ /(\&.{1,6})/g); 
    57         foreach my $and (@ands) { 
    58             if($and ne "&") { 
    59                 my $new_and = $and; 
    60                 $new_and =~ s/\&/\&/; 
    61                 $self->{$key} =~ s/$and/$new_and/; 
    62             } 
    63         } 
    64     } 
    65  
    66     $self; 
    67 } 
    68  
    69 =item <build_feed_start> 
    70  
    71 Internal method, to build all the stuff that will go at the start of a feed. 
    72 Outputs the feed header, and initial feed info. 
    73  
    74 =cut 
    75  
    76 sub build_feed_start { 
    77     my ($self,$atom_timestamp) = @_; 
    78  
    79     my $generator = ''; 
    80    
    81     if ($self->{software_name}) { 
    82         $generator  = '  <generator'; 
    83         $generator .= ' uri="' . $self->{software_homepage} . '"'   if $self->{software_homepage}; 
    84         $generator .= ' version=' . $self->{software_version} . '"' if $self->{software_version}; 
    85         $generator .= ">\n"; 
    86         $generator .= $self->{software_name} . "</generator>\n"; 
    87     }                           
    88  
    89     my $subtitle = $self->{site_description} 
    90                  ? '<subtitle>' . $self->{site_description} . "</subtitle>\n" 
    91                  : ''; 
    92  
    93     $atom_timestamp ||= ''; 
    94  
    95     my $atom = qq{<?xml version="1.0" encoding="} . $self->{encoding} . qq{"?> 
    96  
    97 <feed  
    98  xmlns         = "http://www.w3.org/2005/Atom" 
    99  xmlns:geo     = "http://www.w3.org/2003/01/geo/wgs84_pos#" 
    100  xmlns:space   = "http://frot.org/space/0.1/" 
    101 > 
    102  
    103   <link href="}            . $self->{site_url}     . qq{" /> 
    104   <title>}                 . $self->{site_name}    . qq{</title> 
    105   <link rel="self" href="} . $self->{atom_link}    . qq{" /> 
    106   <updated>}               . $atom_timestamp       . qq{</updated> 
    107   <id>}                    . $self->{site_url}     . qq{</id> 
    108   $subtitle}; 
    109    
    110     return $atom; 
    111 } 
    112  
    113 =item <build_feed_end> 
    114  
    115 Internal method, to build all the stuff that will go at the end of a feed. 
    116  
    117 =cut 
    118  
    119 sub build_feed_end { 
    120     my ($self,$feed_timestamp) = @_; 
    121  
    122     return "</feed>\n"; 
    123 } 
    124  
    125 =item <generate_node_list_feed> 
    126    
    127 Generate and return an Atom feed for a list of nodes 
    128    
    129 =cut 
    130  
    131 sub generate_node_list_feed { 
    132     my ($self,$atom_timestamp,@nodes) = @_; 
    133  
    134     my $atom = $self->build_feed_start($atom_timestamp); 
    135  
    136     my (@urls, @items); 
    137  
    138     foreach my $node (@nodes) { 
    139         my $node_name = $node->{name}; 
    140  
    141         my $item_timestamp = $node->{last_modified}; 
    142      
    143         # Make a Time::Piece object. 
    144         my $time = Time::Piece->strptime($item_timestamp, $self->{timestamp_fmt}); 
    145  
    146         my $utc_offset = $self->{utc_offset}; 
    147      
    148         $item_timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S$utc_offset" ); 
    149  
    150         my $author      = $node->{metadata}{username}[0] || $node->{metadata}{host}[0] || 'Anonymous'; 
    151         my $description = $node->{metadata}{comment}[0]  || 'No description given for node'; 
    152  
    153         $description .= " [$author]" if $author; 
    154  
    155         my $version = $node->{version}; 
    156         my $status  = (1 == $version) ? 'new' : 'updated'; 
    157  
    158         my $major_change = $node->{metadata}{major_change}[0]; 
    159         $major_change = 1 unless defined $major_change; 
    160         my $importance = $major_change ? 'major' : 'minor'; 
    161  
    162         my $url = $self->{make_node_url}->($node_name, $version); 
    163  
    164         # make XML-clean 
    165         my $title =  $node_name; 
    166         $title =~ s/&/&amp;/g; 
    167         $title =~ s/</&lt;/g; 
    168         $title =~ s/>/&gt;/g; 
    169  
    170         # Pop the categories into atom:category elements (4.2.2) 
    171         # We can do this because the spec says: 
    172         #   "This specification assigns no meaning to the content (if any)  
    173         #    of this element." 
    174         # TODO: Decide if we should include the "all categories listing" url 
    175         #        as the scheme (URI) attribute? 
    176         my $category_atom = ""; 
    177         if ($node->{metadata}->{category}) { 
    178             foreach my $cat (@{ $node->{metadata}->{category} }) { 
    179                 $category_atom .= "    <category term=\"$cat\" />\n"; 
    180             } 
    181         } 
    182  
    183         # Include geospacial data, if we have it 
    184         my $geo_atom = $self->format_geo($node->{metadata}); 
    185  
    186         # TODO: Find an Atom equivalent of ModWiki, so we can include more info 
    187  
    188      
    189         push @items, qq{ 
    190   <entry> 
    191     <title>$title</title> 
    192     <link href="$url" /> 
    193     <id>$url</id> 
    194     <summary>$description</summary> 
    195     <updated>$item_timestamp</updated> 
    196     <author><name>$author</name></author> 
    197 $category_atom 
    198 $geo_atom 
    199   </entry> 
    200 }; 
    201  
    202     } 
    203    
    204     $atom .= join('', @items) . "\n"; 
    205     $atom .= $self->build_feed_end($atom_timestamp); 
    206  
    207     return $atom;    
    208 } 
    209  
    210 =item <generate_node_name_distance_feed> 
    211    
    212 Generate a very cut down atom feed, based just on the nodes, their locations 
    213 (if given), and their distance from a reference location (if given). 
    214  
    215 Typically used on search feeds. 
    216    
    217 =cut 
    218  
    219 sub generate_node_name_distance_feed { 
    220     my ($self,$atom_timestamp,@nodes) = @_; 
    221  
    222     my $atom = $self->build_feed_start($atom_timestamp); 
    223  
    224     my (@urls, @items); 
    225  
    226     foreach my $node (@nodes) { 
    227         my $node_name = $node->{name}; 
    228  
    229         my $url = $self->{make_node_url}->($node_name); 
    230  
    231         # make XML-clean 
    232         my $title =  $node_name; 
    233         $title =~ s/&/&amp;/g; 
    234         $title =~ s/</&lt;/g; 
    235         $title =~ s/>/&gt;/g; 
    236  
    237         # What location stuff do we have? 
    238         my $geo_atom = $self->format_geo($node); 
    239  
    240         push @items, qq{ 
    241   <entry> 
    242     <title>$title</title> 
    243     <link href="$url" /> 
    244     <id>$url</id> 
    245 $geo_atom 
    246   </entry> 
    247 }; 
    248  
    249     } 
    250    
    251     $atom .= join('', @items) . "\n"; 
    252     $atom .= $self->build_feed_end($atom_timestamp); 
    253  
    254     return $atom;    
    255 } 
    256  
    257 =item B<feed_timestamp> 
    258  
    259 Generate the timestamp for the Atom, based on the newest node (if available). 
    260 Will return a timestamp for now if no node dates are available 
    261  
    262 =cut 
    263  
    264 sub feed_timestamp { 
    265     my ($self, $newest_node) = @_; 
    266    
    267     my $time; 
    268     if ($newest_node->{last_modified}) { 
    269         $time = Time::Piece->strptime( $newest_node->{last_modified}, $self->{timestamp_fmt} ); 
    270     } else { 
    271         $time = localtime; 
    272     } 
    273  
    274     my $utc_offset = $self->{utc_offset}; 
    275      
    276     return $time->strftime( "%Y-%m-%dT%H:%M:%S$utc_offset" ); 
    277 } 
    278  
    279  
    280 =item B<parse_feed_timestamp> 
    281  
    282 Take a feed_timestamp and return a Time::Piece object.  
    283  
    284 =cut 
    285  
    286 sub parse_feed_timestamp { 
    287     my ($self, $feed_timestamp) = @_; 
    288     
    289     $feed_timestamp = substr($feed_timestamp, 0, -length( $self->{utc_offset})); 
    290     return Time::Piece->strptime( $feed_timestamp, '%Y-%m-%dT%H:%M:%S' ); 
    291 } 
    292 1; 
    293  
    294 __END__ 
    29515 
    29616=head1 NAME 
     
    451171=back 
    452172 
     173=cut 
     174 
     175sub new { 
     176    my $class = shift; 
     177    my $self  = {}; 
     178    bless $self, $class; 
     179 
     180    my %args = @_; 
     181    my $wiki = $args{wiki}; 
     182 
     183    unless ($wiki && UNIVERSAL::isa($wiki, 'Wiki::Toolkit')) { 
     184        croak 'No Wiki::Toolkit object supplied'; 
     185    } 
     186   
     187    $self->{wiki} = $wiki; 
     188   
     189    # Mandatory arguments. 
     190    foreach my $arg (qw/site_name site_url make_node_url atom_link/) { 
     191        croak "No $arg supplied" unless $args{$arg}; 
     192        $self->{$arg} = $args{$arg}; 
     193    } 
     194 
     195    # Must-supply-one-of arguments 
     196    my %mustoneof = ( 'html_equiv_link' => ['html_equiv_link','recent_changes_link'] ); 
     197    $self->handle_supply_one_of(\%mustoneof,\%args); 
     198   
     199    # Optional arguments. 
     200    foreach my $arg (qw/site_description software_name software_version software_homepage encoding/) { 
     201        $self->{$arg} = $args{$arg} || ''; 
     202    } 
     203 
     204    # Supply some defaults, if a blank string isn't what we want 
     205    unless($self->{encoding}) { 
     206        $self->{encoding} = $self->{wiki}->store->{_charset}; 
     207    } 
     208 
     209    $self->{timestamp_fmt} = $Wiki::Toolkit::Store::Database::timestamp_fmt; 
     210    $self->{utc_offset} = strftime "%z", localtime; 
     211    $self->{utc_offset} =~ s/(..)(..)$/$1:$2/; 
     212   
     213    # Escape any &'s in the urls 
     214    foreach my $key qw(site_url atom_link) { 
     215        my @ands = ($self->{$key} =~ /(\&.{1,6})/g); 
     216        foreach my $and (@ands) { 
     217            if($and ne "&amp;") { 
     218                my $new_and = $and; 
     219                $new_and =~ s/\&/\&amp;/; 
     220                $self->{$key} =~ s/$and/$new_and/; 
     221            } 
     222        } 
     223    } 
     224 
     225    $self; 
     226} 
     227 
     228# Internal method, to build all the stuff that will go at the start of a feed. 
     229# Outputs the feed header, and initial feed info. 
     230 
     231sub build_feed_start { 
     232    my ($self,$atom_timestamp) = @_; 
     233 
     234    my $generator = ''; 
     235   
     236    if ($self->{software_name}) { 
     237        $generator  = '  <generator'; 
     238        $generator .= ' uri="' . $self->{software_homepage} . '"'   if $self->{software_homepage}; 
     239        $generator .= ' version=' . $self->{software_version} . '"' if $self->{software_version}; 
     240        $generator .= ">\n"; 
     241        $generator .= $self->{software_name} . "</generator>\n"; 
     242    }                           
     243 
     244    my $subtitle = $self->{site_description} 
     245                 ? '<subtitle>' . $self->{site_description} . "</subtitle>\n" 
     246                 : ''; 
     247 
     248    $atom_timestamp ||= ''; 
     249 
     250    my $atom = qq{<?xml version="1.0" encoding="} . $self->{encoding} . qq{"?> 
     251 
     252<feed  
     253 xmlns         = "http://www.w3.org/2005/Atom" 
     254 xmlns:geo     = "http://www.w3.org/2003/01/geo/wgs84_pos#" 
     255 xmlns:space   = "http://frot.org/space/0.1/" 
     256> 
     257 
     258  <link href="}            . $self->{site_url}     . qq{" /> 
     259  <title>}                 . $self->{site_name}    . qq{</title> 
     260  <link rel="self" href="} . $self->{atom_link}    . qq{" /> 
     261  <updated>}               . $atom_timestamp       . qq{</updated> 
     262  <id>}                    . $self->{site_url}     . qq{</id> 
     263  $subtitle}; 
     264   
     265    return $atom; 
     266} 
     267 
     268# Internal method, to build all the stuff that will go at the end of a feed. 
     269 
     270sub build_feed_end { 
     271    my ($self,$feed_timestamp) = @_; 
     272 
     273    return "</feed>\n"; 
     274} 
     275 
     276=head2 C<generate_node_list_feed> 
     277   
     278Generate and return an Atom feed for a list of nodes 
     279   
     280=cut 
     281 
     282sub generate_node_list_feed { 
     283    my ($self,$atom_timestamp,@nodes) = @_; 
     284 
     285    my $atom = $self->build_feed_start($atom_timestamp); 
     286 
     287    my (@urls, @items); 
     288 
     289    foreach my $node (@nodes) { 
     290        my $node_name = $node->{name}; 
     291 
     292        my $item_timestamp = $node->{last_modified}; 
     293     
     294        # Make a Time::Piece object. 
     295        my $time = Time::Piece->strptime($item_timestamp, $self->{timestamp_fmt}); 
     296 
     297        my $utc_offset = $self->{utc_offset}; 
     298     
     299        $item_timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S$utc_offset" ); 
     300 
     301        my $author      = $node->{metadata}{username}[0] || $node->{metadata}{host}[0] || 'Anonymous'; 
     302        my $description = $node->{metadata}{comment}[0]  || 'No description given for node'; 
     303 
     304        $description .= " [$author]" if $author; 
     305 
     306        my $version = $node->{version}; 
     307        my $status  = (1 == $version) ? 'new' : 'updated'; 
     308 
     309        my $major_change = $node->{metadata}{major_change}[0]; 
     310        $major_change = 1 unless defined $major_change; 
     311        my $importance = $major_change ? 'major' : 'minor'; 
     312 
     313        my $url = $self->{make_node_url}->($node_name, $version); 
     314 
     315        # make XML-clean 
     316        my $title =  $node_name; 
     317        $title =~ s/&/&amp;/g; 
     318        $title =~ s/</&lt;/g; 
     319        $title =~ s/>/&gt;/g; 
     320 
     321        # Pop the categories into atom:category elements (4.2.2) 
     322        # We can do this because the spec says: 
     323        #   "This specification assigns no meaning to the content (if any)  
     324        #    of this element." 
     325        # TODO: Decide if we should include the "all categories listing" url 
     326        #        as the scheme (URI) attribute? 
     327        my $category_atom = ""; 
     328        if ($node->{metadata}->{category}) { 
     329            foreach my $cat (@{ $node->{metadata}->{category} }) { 
     330                $category_atom .= "    <category term=\"$cat\" />\n"; 
     331            } 
     332        } 
     333 
     334        # Include geospacial data, if we have it 
     335        my $geo_atom = $self->format_geo($node->{metadata}); 
     336 
     337        # TODO: Find an Atom equivalent of ModWiki, so we can include more info 
     338 
     339     
     340        push @items, qq{ 
     341  <entry> 
     342    <title>$title</title> 
     343    <link href="$url" /> 
     344    <id>$url</id> 
     345    <summary>$description</summary> 
     346    <updated>$item_timestamp</updated> 
     347    <author><name>$author</name></author> 
     348$category_atom 
     349$geo_atom 
     350  </entry> 
     351}; 
     352 
     353    } 
     354   
     355    $atom .= join('', @items) . "\n"; 
     356    $atom .= $self->build_feed_end($atom_timestamp); 
     357 
     358    return $atom;    
     359} 
     360 
     361=head2 C<generate_node_name_distance_feed> 
     362   
     363Generate a very cut down atom feed, based just on the nodes, their locations 
     364(if given), and their distance from a reference location (if given). 
     365 
     366Typically used on search feeds. 
     367   
     368=cut 
     369 
     370sub generate_node_name_distance_feed { 
     371    my ($self,$atom_timestamp,@nodes) = @_; 
     372 
     373    my $atom = $self->build_feed_start($atom_timestamp); 
     374 
     375    my (@urls, @items); 
     376 
     377    foreach my $node (@nodes) { 
     378        my $node_name = $node->{name}; 
     379 
     380        my $url = $self->{make_node_url}->($node_name); 
     381 
     382        # make XML-clean 
     383        my $title =  $node_name; 
     384        $title =~ s/&/&amp;/g; 
     385        $title =~ s/</&lt;/g; 
     386        $title =~ s/>/&gt;/g; 
     387 
     388        # What location stuff do we have? 
     389        my $geo_atom = $self->format_geo($node); 
     390 
     391        push @items, qq{ 
     392  <entry> 
     393    <title>$title</title> 
     394    <link href="$url" /> 
     395    <id>$url</id> 
     396$geo_atom 
     397  </entry> 
     398}; 
     399 
     400    } 
     401   
     402    $atom .= join('', @items) . "\n"; 
     403    $atom .= $self->build_feed_end($atom_timestamp); 
     404 
     405    return $atom;    
     406} 
     407 
    453408=head2 C<feed_timestamp()> 
    454409 
     
    460415need this to print a Last-Modified HTTP header so user-agents can determine 
    461416whether they need to reload the feed or not. 
    462    
     417 
     418=cut 
     419 
     420sub feed_timestamp { 
     421    my ($self, $newest_node) = @_; 
     422   
     423    my $time; 
     424    if ($newest_node->{last_modified}) { 
     425        $time = Time::Piece->strptime( $newest_node->{last_modified}, $self->{timestamp_fmt} ); 
     426    } else { 
     427        $time = localtime; 
     428    } 
     429 
     430    my $utc_offset = $self->{utc_offset}; 
     431     
     432    return $time->strftime( "%Y-%m-%dT%H:%M:%S$utc_offset" ); 
     433} 
     434 
     435 
     436=head2 C<parse_feed_timestamp> 
     437 
     438Take a feed_timestamp and return a Time::Piece object.  
     439 
     440=cut 
     441 
     442sub parse_feed_timestamp { 
     443    my ($self, $feed_timestamp) = @_; 
     444    
     445    $feed_timestamp = substr($feed_timestamp, 0, -length( $self->{utc_offset})); 
     446    return Time::Piece->strptime( $feed_timestamp, '%Y-%m-%dT%H:%M:%S' ); 
     447} 
     4481; 
     449 
     450__END__ 
     451 
     452 
    463453=head1 SEE ALSO 
    464454 
     
    477467=head1 COPYRIGHT AND LICENSE 
    478468 
    479 Copyright 2006-2008 Earle Martin and the Wiki::Toolkit team. 
     469Copyright 2006-2009 Earle Martin and the Wiki::Toolkit team. 
    480470 
    481471This module is free software; you can redistribute it and/or modify it