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

Last change on this file since 785 was 785, checked in by Dominic Hargreaves, 15 years ago

Use Wiki::Toolkit. Note that CGI::Wiki::Plugin::Diff hasn't been converted yet,
so tests will be broken for now.

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