source: trunk/lib/OpenGuides/RDF.pm @ 606

Last change on this file since 606 was 587, checked in by kake, 17 years ago

Encapsulate config data in OpenGuides::Config.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 10.5 KB
Line 
1package OpenGuides::RDF;
2
3use strict;
4
5use vars qw( $VERSION );
6$VERSION = '0.06';
7
8use CGI::Wiki::Plugin::RSS::ModWiki;
9use Time::Piece;
10use URI::Escape;
11use Carp qw( croak );
12
13=head1 NAME
14
15OpenGuides::RDF - An OpenGuides plugin to output RDF/XML.
16
17=head1 DESCRIPTION
18
19Does all the RDF stuff for OpenGuides.  Distributed and installed as
20part of the OpenGuides project, not intended for independent
21installation.  This documentation is probably only useful to OpenGuides
22developers.
23
24=head1 SYNOPSIS
25
26  use CGI::Wiki;
27  use OpenGuides::Config;
28  use OpenGuides::RDF;
29
30  my $wiki = CGI::Wiki->new( ... );
31  my $config = OpenGuides::Config->new( file => "wiki.conf" );
32  my $rdf_writer = OpenGuides::RDF->new( wiki   => $wiki,
33                                         config => $config );
34
35  # RDF version of a node.
36  print "Content-type: text/plain\n\n";
37  print $rdf_writer->emit_rdfxml( node => "Masala Zone, N1 0NU" );
38
39  # Ten most recent changes.
40  print "Content-type: text/plain\n\n";
41  print $rdf_writer->make_recentchanges_rss( items => 10 );
42
43=head1 METHODS
44
45=over 4
46
47=item B<new>
48
49  my $rdf_writer = OpenGuides::RDF->new( wiki   => $wiki,
50                                         config => $config );
51
52C<wiki> must be a L<CGI::Wiki> object and C<config> must be an
53L<OpenGuides::Config> object.  Both arguments mandatory.
54
55=cut
56
57sub new {
58    my ($class, @args) = @_;
59    my $self = {};
60    bless $self, $class;
61    return $self->_init(@args);
62}
63
64sub _init {
65    my ($self, %args) = @_;
66
67    my $wiki = $args{wiki};
68    unless ( $wiki && UNIVERSAL::isa( $wiki, "CGI::Wiki" ) ) {
69        croak "No CGI::Wiki object supplied.";
70    }
71    $self->{wiki} = $wiki;
72
73    my $config = $args{config};
74    unless ( $config && UNIVERSAL::isa( $config, "OpenGuides::Config" ) ) {
75        croak "No OpenGuides::Config object supplied.";
76    }
77    $self->{config} = $config;
78
79    $self->{site_name} = $config->site_name;
80    $self->{make_node_url} = sub {
81        my ($node_name, $version) = @_;
82        if ( defined $version ) {
83            return $config->script_url . uri_escape($config->script_name) . "?id=" . uri_escape($wiki->formatter->node_name_to_node_param($node_name)) . ";version=" . uri_escape($version);
84        } else {
85            return $config->script_url . uri_escape($config->script_name) . "?id=" . uri_escape($wiki->formatter->node_name_to_node_param($node_name));
86        }
87    };
88    $self->{default_city}     = $config->default_city     || "";
89    $self->{default_country}  = $config->default_country  || "";
90    $self->{site_description} = $config->site_desc        || "";
91
92    return $self;
93}
94
95=cut
96
97=item B<emit_rdfxml>
98
99  $wiki->write_node( "Masala Zone, N1 0NU",
100                     "Quick and tasty Indian food",
101                     $checksum,
102                     { comment  => "New page",
103                       username => "Kake",
104                       locale   => "Islington"
105                     }
106  );
107
108  print "Content-type: text/plain\n\n";
109  print $rdf_writer->emit_rdfxml( node => "Masala Zone, N1 0NU" );
110
111B<Note:> Some of the fields emitted by the RDF/XML generator are taken
112from the node metadata. The form of this metadata is I<not> mandated
113by L<CGI::Wiki>. Your wiki application should make sure to store some or
114all of the following metadata when calling C<write_node>:
115
116=over 4
117
118=item B<postcode> - The postcode or zip code of the place discussed by the node.  Defaults to the empty string.
119
120=item B<city> - The name of the city that the node is in.  If not supplied, then the value of C<default_city> in the config object supplied to C<new>, if available, otherwise the empty string.
121
122=item B<country> - The name of the country that the node is in.  If not supplied, then the value of C<default_country> in the config object supplied to C<new> will be used, if available, otherwise the empty string.
123
124=item B<username> - An identifier for the person who made the latest edit to the node.  This person will be listed as a contributor (Dublin Core).  Defaults to empty string.
125
126=item B<locale> - The value of this can be a scalar or an arrayref, since some places have a plausible claim to being in more than one locale.  Each of these is put in as a C<Neighbourhood> attribute.
127
128=item B<phone> - Only one number supported at the moment.  No validation.
129
130=item B<website> - No validation.
131
132=item B<opening_hours_text> - A freeform text field.
133
134=back
135
136=cut
137
138sub emit_rdfxml {
139    my ($self, %args) = @_;
140
141    my $node_name = $args{node};
142    my $wiki = $self->{wiki};
143
144    my %node_data          = $wiki->retrieve_node( $node_name );
145    my $phone              = $node_data{metadata}{phone}[0]              || '';
146    my $fax                = $node_data{metadata}{fax}[0]                || '';
147    my $website            = $node_data{metadata}{website}[0]            || '';
148    my $opening_hours_text = $node_data{metadata}{opening_hours_text}[0] || '';
149    my $postcode           = $node_data{metadata}{postcode}[0]           || '';
150    my $city               = $node_data{metadata}{city}[0]               || $self->{default_city};
151    my $country            = $node_data{metadata}{country}[0]            || $self->{default_country};
152    my $latitude           = $node_data{metadata}{latitude}[0]           || '';
153    my $longitude          = $node_data{metadata}{longitude}[0]          || '';
154    my $version            = $node_data{version};
155    my $username           = $node_data{metadata}{username}[0]           || '';
156    my $os_x               = $node_data{metadata}{os_x}[0]               || '';
157    my $os_y               = $node_data{metadata}{os_y}[0]               || '';
158    my $catrefs            = $node_data{metadata}{category};
159    my @locales            = @{ $node_data{metadata}{locale} || [] };
160
161    my ($is_geospatial, $objType);
162
163    if ($latitude || $longitude || $postcode || $city || $country || @locales)
164    {
165      $is_geospatial = 1;
166      $objType    = 'geo:SpatialThing';
167    }
168    else
169    {
170      $objType = 'rdf:Description';
171    }
172
173    my $timestamp = $node_data{last_modified};
174    # Make a Time::Piece object.
175    my $timestamp_fmt = $CGI::Wiki::Store::Database::timestamp_fmt;
176#        my $timestamp_fmt = $wiki->{store}->timestamp_fmt;
177    if ( $timestamp ) {
178        my $time = Time::Piece->strptime( $timestamp, $timestamp_fmt );
179        $timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S" );
180    }
181
182    my $url = $self->{make_node_url}->( $node_name, $version );
183    my $version_indpt_uri = $self->{make_node_url}->( $node_name );
184
185    my $rdf = qq{<?xml version="1.0"?>
186  <rdf:RDF
187    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
188    xmlns:dc="http://purl.org/dc/elements/1.1/"
189    xmlns:dcterms="http://purl.org/dc/terms/"
190    xmlns:foaf="http://xmlns.com/foaf/0.1/"
191    xmlns:wiki="http://purl.org/rss/1.0/modules/wiki/"
192    xmlns:chefmoz="http://chefmoz.org/rdf/elements/1.0/"
193    xmlns:wn="http://xmlns.com/wordnet/1.6/"
194    xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
195    xmlns:os="http://downlode.org/rdf/os/0.1/"
196    xmlns="http://www.w3.org/2000/10/swap/pim/contact#"
197  >
198
199  <rdf:Description rdf:about="">
200    <dc:title>} . $self->{site_name} . qq{: $node_name</dc:title>
201    <dc:date>$timestamp</dc:date>
202    <dcterms:modified>$timestamp</dcterms:modified>
203    <dc:contributor>$username</dc:contributor>
204    <dc:source rdf:resource="$version_indpt_uri" />
205    <wiki:version>$version</wiki:version>
206    <foaf:topic rdf:resource="#obj" />
207  </rdf:Description>
208
209  <$objType rdf:ID="obj" dc:title="$node_name">
210};
211    $rdf .= "    <!-- categories -->\n";
212    $rdf .= "    <dc:subject>$_</dc:subject>\n"             foreach @{$catrefs};
213    $rdf .= "\n    <!-- address and geospatial data -->\n";
214    $rdf .= "    <city>$city</city>"                        if $is_geospatial;
215    $rdf .= "    <postalCode>$postcode</postalCode>\n"      if $postcode;
216    $rdf .= "    <country>$country</country>\n"             if $is_geospatial;
217    $rdf .= "    <foaf:based_near><wn:Neighborhood><foaf:name>$_</foaf:name></wn:Neighborhood></foaf:based_near>\n"   foreach @locales;
218
219    if ($latitude && $longitude) {
220      $rdf .= qq{    <geo:lat>$latitude</geo:lat>
221    <geo:long>$longitude</geo:long>
222};
223    }
224
225    if ($os_x && $os_y) {
226      $rdf .= qq{    <os:x>$os_x</os:x>
227    <os:y>$os_y</os:y>};
228    }
229
230    $rdf .= "\n    <!-- contact information -->\n";
231    $rdf .= "    <phone>$phone</phone>\n"                              if $phone;
232    $rdf .= "    <fax>$fax</fax>\n"                                    if $fax;
233    $rdf .= "    <homePage>$website</homePage>\n"                      if $website;
234    $rdf .= "    <chefmoz:Hours>$opening_hours_text</chefmoz:Hours>\n" if $opening_hours_text;
235
236    $rdf .= qq{
237  </$objType>
238</rdf:RDF>
239
240};
241
242    return $rdf;
243}
244
245=item B<make_recentchanges_rss>
246
247  # Ten most recent changes.
248  print "Content-type: text/plain\n\n";
249  print $rdf_writer->make_recentchanges_rss(
250                                             items => 10,
251                                           );
252
253  # All the changes made by bob in the past week, ignoring minor edits.
254  print "Content-type: text/plain\n\n";
255  print $rdf_writer->make_recentchanges_rss(
256            days               => 7,
257            ignore_minor_edits => 1,
258            filter_on_metadata => { username => "bob" },
259                                           );
260
261=cut
262
263sub make_recentchanges_rss {
264    my ($self, %args) = @_;
265
266    my $rssmaker = CGI::Wiki::Plugin::RSS::ModWiki->new(
267        wiki      => $self->{wiki},
268        site_name => $self->{site_name},
269        site_description => $self->{site_description},
270        make_node_url => $self->{make_node_url},
271        recent_changes_link => $self->{config}->script_url . uri_escape($self->{config}->script_name) . "?RecentChanges"
272     );
273
274    my %criteria;
275    $criteria{items} = $args{items} if $args{items};
276    $criteria{days} = $args{days} if $args{days};
277    $criteria{ignore_minor_changes} = $args{ignore_minor_edits}
278                                                  if $args{ignore_minor_edits};
279    $criteria{filter_on_metadata} = $args{filter_on_metadata}
280                                                  if $args{filter_on_metadata};
281    return $rssmaker->recent_changes( %criteria );
282}
283
284=back
285
286=head1 SEE ALSO
287
288=over 4
289
290=item * L<CGI::Wiki>
291
292=item * L<http://openguides.org/>
293
294=item * L<http://chefmoz.org/>
295
296=back
297
298=head1 AUTHOR
299
300The OpenGuides Project (openguides-dev@openguides.org)
301
302=head1 COPYRIGHT
303
304     Copyright (C) 2003-2004 The OpenGuides Project.  All Rights Reserved.
305
306This module is free software; you can redistribute it and/or modify it
307under the same terms as Perl itself.
308
309=head1 CREDITS
310
311Code in this module written by Kake Pugh and Earle Martin.  Dan
312Brickley, Matt Biddulph and other inhabitants of #core gave useful
313feedback and advice.
314
315=cut
316
3171;
Note: See TracBrowser for help on using the repository browser.