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

Last change on this file since 767 was 767, checked in by Earle Martin, 15 years ago

crschmidt pointed out that spaces in usernames would create invalid IDs

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