line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package RDF::AllegroGraph::Repository4; |
2
|
|
|
|
|
|
|
|
3
|
15
|
|
|
15
|
|
84
|
use strict; |
|
15
|
|
|
|
|
217
|
|
|
15
|
|
|
|
|
503
|
|
4
|
15
|
|
|
15
|
|
79
|
use warnings; |
|
15
|
|
|
|
|
28
|
|
|
15
|
|
|
|
|
455
|
|
5
|
|
|
|
|
|
|
|
6
|
15
|
|
|
15
|
|
77
|
use base qw(RDF::AllegroGraph::Repository); |
|
15
|
|
|
|
|
30
|
|
|
15
|
|
|
|
|
2265
|
|
7
|
|
|
|
|
|
|
|
8
|
15
|
|
|
15
|
|
84
|
use Data::Dumper; |
|
15
|
|
|
|
|
36
|
|
|
15
|
|
|
|
|
969
|
|
9
|
15
|
|
|
15
|
|
88
|
use feature "switch"; |
|
15
|
|
|
|
|
23
|
|
|
15
|
|
|
|
|
1368
|
|
10
|
|
|
|
|
|
|
|
11
|
15
|
|
|
15
|
|
77
|
use JSON; |
|
15
|
|
|
|
|
50
|
|
|
15
|
|
|
|
|
100
|
|
12
|
15
|
|
|
15
|
|
1839
|
use URI::Escape qw/uri_escape_utf8/; |
|
15
|
|
|
|
|
30
|
|
|
15
|
|
|
|
|
1048
|
|
13
|
|
|
|
|
|
|
|
14
|
15
|
|
|
15
|
|
2038
|
use HTTP::Request::Common; |
|
15
|
|
|
|
|
4618
|
|
|
15
|
|
|
|
|
10800
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=pod |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 NAME |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
RDF::AllegroGraph::Repository4 - AllegroGraph repository handle for AGv4 |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 INTERFACE |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
Same as L from which we inherit. |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=cut |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
sub new { |
29
|
0
|
|
|
0
|
0
|
|
my $class = shift; |
30
|
0
|
|
|
|
|
|
my %options = @_; |
31
|
0
|
|
|
|
|
|
my $self = bless \%options, $class; |
32
|
0
|
0
|
|
|
|
|
$self->{path} = $self->{CATALOG}->{SERVER}->{ADDRESS} . ($self->{CATALOG}->{NAME} eq '/' |
33
|
|
|
|
|
|
|
? '' |
34
|
|
|
|
|
|
|
: '/catalogs' . $self->{CATALOG}->{NAME} ) . '/repositories/' . $self->{id}; |
35
|
0
|
|
|
|
|
|
return $self; |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
=pod |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=over |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=item B |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
This read-only accessor method returns the id of the repository. |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=cut |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub id { |
49
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
50
|
0
|
0
|
|
|
|
|
return $self->{CATALOG}->{NAME} eq '/' |
51
|
|
|
|
|
|
|
? '/' . $self->{id} |
52
|
|
|
|
|
|
|
: $self->{CATALOG}->{NAME} . '/' . $self->{id}; |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=pod |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=item B |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
I<$repo>->disband |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
This method removes the repository from the server. The object cannot be used after that, obviously. |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=cut |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
sub disband { |
66
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
67
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new (DELETE => $self->{path}); |
68
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ); |
69
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=pod |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=item B |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
I<$nr_triples> = I<$repo>->size |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
Returns the size of the repository in terms of the number of triples. |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
B: As of time of writing, AllegroGraph counts duplicate triples! |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=cut |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub size { |
85
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
86
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($self->{path} . '/size'); |
87
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
88
|
0
|
|
|
|
|
|
return $resp->content; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=pod |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=back |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=head2 Methods (over those we inherit) |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=over |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=item B (since v0.06) |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
I<$session> = I<$repo>->session |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
This method forks a session out of the current repository session. Unlike a transaction, all changes |
104
|
|
|
|
|
|
|
are autocommitted into the mother repository. But AG4 needs a separate connection thread for some |
105
|
|
|
|
|
|
|
specific features (SNA, loading Prolog knowledge, etc.) |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=cut |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub session { |
110
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
111
|
0
|
|
|
|
|
|
my %opts = @_; |
112
|
0
|
|
0
|
|
|
|
$opts{autoCommit} ||= 'true'; # this is - by default - a session, so whatever we do, it will shine through |
113
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->post ($self->{path} . '/session', %opts); |
114
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
115
|
0
|
|
|
|
|
|
my $url = $resp->content; |
116
|
0
|
|
|
|
|
|
$url =~ s/^\"//; $url =~ s/\"$//; # for some very odd reason we get this URI in the content |
|
0
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
{ # try to find authentication information inside the repo URL |
118
|
0
|
0
|
|
|
|
|
if ($self->{path} =~ m{http://(.+?@)}) { |
|
0
|
|
|
|
|
|
|
119
|
0
|
|
|
|
|
|
my $auth = $1; |
120
|
0
|
|
|
|
|
|
$url =~ s{http://}{http://$auth}; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
} |
123
|
0
|
0
|
|
|
|
|
if ($opts{autoCommit} eq 'true') { # we do like 'true' strings ... |
124
|
15
|
|
|
15
|
|
18155
|
use RDF::AllegroGraph::Session4; |
|
15
|
|
|
|
|
38
|
|
|
15
|
|
|
|
|
1158
|
|
125
|
0
|
|
|
|
|
|
return new RDF::AllegroGraph::Session4 (path => $url, # the newly returned URL will be its home |
126
|
|
|
|
|
|
|
CATALOG => $self->{CATALOG}); # and the catalog will be the same |
127
|
|
|
|
|
|
|
} else { |
128
|
15
|
|
|
15
|
|
15799
|
use RDF::AllegroGraph::Transaction4; |
|
15
|
|
|
|
|
43
|
|
|
15
|
|
|
|
|
5670
|
|
129
|
0
|
|
|
|
|
|
return new RDF::AllegroGraph::Transaction4 (path => $url, # the newly returned URL will be its home |
130
|
|
|
|
|
|
|
CATALOG => $self->{CATALOG}); # and the catalog will be the same |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=pod |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=item B (since v0.06) |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
I<$tx> = I<$repo>->transaction |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
This method forks a transaction out of the current repository session. That transaction is itself a |
141
|
|
|
|
|
|
|
repository session (and a session, for that matter). Whatever you do in the transaction, will stay |
142
|
|
|
|
|
|
|
in the transaction. With calling the C method (see L), |
143
|
|
|
|
|
|
|
you will simply empty the transaction. That is also the default behaviour, if the transaction object |
144
|
|
|
|
|
|
|
goes out of scope. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
To manifest any changes you will have to invoke C on the transaction object. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=cut |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
sub transaction { |
151
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
152
|
0
|
|
|
|
|
|
return $self->session (autoCommit => 'false'); |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=pod |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=item B (since v0.06) |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
I<@blanks> = I<$repo>->blanks (I) |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
This method asks the server to create a number of blank nodes in the repository. The ids of these |
162
|
|
|
|
|
|
|
nodes will be returned. By default, one node will be created, but you can ask for more. |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=cut |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
sub blanks { |
167
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
168
|
0
|
|
0
|
|
|
|
my $amount = shift || 1; |
169
|
|
|
|
|
|
|
|
170
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->post ($self->{path} . '/blankNodes', |
171
|
|
|
|
|
|
|
'Content-Type' => 'application/x-www-form-urlencoded', |
172
|
|
|
|
|
|
|
'Accept' => 'application/json', |
173
|
|
|
|
|
|
|
'Content' => { 'amount' => $amount }); |
174
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
175
|
0
|
|
|
|
|
|
return @{ from_json ($resp->content) }; |
|
0
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=pod |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=item B |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
I<$repo>->add ('file://....', ...) |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
I<$repo>->add ('http://....', ...) |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
I<$repo>->add (' triples in N3 ', ...) |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
I<$repo>->add ([ I<$subj_uri>, I<$pred_uri>, I<$obj_uri> ], ...) |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
This method adds triples to the repository. The information can be provided in any of the following |
191
|
|
|
|
|
|
|
ways (also mixed): |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=over |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=item file, HTTP, FTP URL |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
If a string looks like an URL, it will be dereferenced, the contents of the resource consulted and |
198
|
|
|
|
|
|
|
that shipped to the repository on the server. If the resource cannot be read, an exception C
|
199
|
|
|
|
|
|
|
not open> will be raised. Any number of these URLs can be provided as parameter. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
B: Only N3 files are supported, and also only when the URL ends with the extension C or |
202
|
|
|
|
|
|
|
C. |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=item N3 triple string |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
If the string looks like N3 notated triples, that content is shipped to the server. |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=item ARRAY reference |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
The reference is interpreted as one triple (statement), containing 3 URIs. These will be shipped |
211
|
|
|
|
|
|
|
as-is to the server. |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=back |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
If the server chokes on any of the above, an exception C is raised. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
B: There are no precautions for over-large content. Yet. |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
B: Named graphs (aka I) are not handled. Yet. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=cut |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
sub add { |
226
|
0
|
|
|
0
|
1
|
|
_put_post_stmts ('POST', @_); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
sub _put_post_stmts { |
230
|
0
|
|
|
0
|
|
|
my $method = shift; |
231
|
0
|
|
|
|
|
|
my $self = shift; |
232
|
|
|
|
|
|
|
|
233
|
0
|
|
|
|
|
|
my @stmts; # collect triples there |
234
|
|
|
|
|
|
|
my $n3; # collect N3 stuff there |
235
|
0
|
|
|
|
|
|
my @files; # collect file names here |
236
|
15
|
|
|
15
|
|
101
|
use Regexp::Common qw/URI/; |
|
15
|
|
|
|
|
30
|
|
|
15
|
|
|
|
|
156
|
|
237
|
|
|
|
|
|
|
|
238
|
0
|
|
|
|
|
|
foreach my $item (@_) { # walk through what we got |
239
|
0
|
0
|
|
|
|
|
if (ref($item) eq 'ARRAY') { # a triple statement |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
240
|
0
|
|
|
|
|
|
push @stmts, $item; |
241
|
|
|
|
|
|
|
} elsif (ref ($item)) { |
242
|
0
|
|
|
|
|
|
die "don't know what to do with it"; |
243
|
|
|
|
|
|
|
} elsif ($item =~ /^$RE{URI}{HTTP}/) { |
244
|
0
|
|
|
|
|
|
push @files, $item; |
245
|
|
|
|
|
|
|
} elsif ($item =~ /^$RE{URI}{FTP}/) { |
246
|
0
|
|
|
|
|
|
push @files, $item; |
247
|
|
|
|
|
|
|
} elsif ($item =~ /^$RE{URI}{file}/) { |
248
|
0
|
|
|
|
|
|
push @files, $item; |
249
|
|
|
|
|
|
|
} else { # scalar => N3 |
250
|
0
|
|
|
|
|
|
$n3 .= $item; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
0
|
|
|
|
|
|
my $ua = $self->{CATALOG}->{SERVER}->{ua}; # local handle |
255
|
|
|
|
|
|
|
|
256
|
0
|
0
|
|
|
|
|
if (@stmts) { # if we have something to say to the server |
257
|
0
|
|
|
|
|
|
given ($method) { |
258
|
0
|
|
|
|
|
|
when ('POST') { |
259
|
0
|
|
|
|
|
|
my $resp = $ua->post ($self->{path} . '/statements', |
260
|
|
|
|
|
|
|
'Content-Type' => 'application/json', 'Content' => encode_json (\@stmts) ); |
261
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
262
|
|
|
|
|
|
|
} |
263
|
0
|
|
|
|
|
|
when ('PUT') { |
264
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new (PUT => $self->{path} . '/statements', |
265
|
|
|
|
|
|
|
[ 'Content-Type' => 'application/json' ], encode_json (\@stmts)); |
266
|
0
|
|
|
|
|
|
my $resp = $ua->request ($requ); |
267
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
268
|
|
|
|
|
|
|
} |
269
|
0
|
|
|
|
|
|
when ('DELETE') { # DELETE |
270
|
|
|
|
|
|
|
# first bulk delete facts, i.e. where there are no wildcards |
271
|
0
|
0
|
0
|
|
|
|
my @facts = grep { defined $_->[0] && defined $_->[1] && defined $_->[2] } @stmts; |
|
0
|
|
|
|
|
|
|
272
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new (POST => $self->{path} . '/statements/delete', |
273
|
|
|
|
|
|
|
[ 'Content-Type' => 'application/json' ], encode_json (\@facts)); |
274
|
0
|
|
|
|
|
|
my $resp = $ua->request ($requ); |
275
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# the delete one by one those with wildcards |
278
|
0
|
|
0
|
|
|
|
my @wildcarded = grep { ! defined $_->[0] || ! defined $_->[1] || ! defined $_->[2] } @stmts; |
|
0
|
|
|
|
|
|
|
279
|
0
|
|
|
|
|
|
foreach my $w (@wildcarded) { |
280
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new (DELETE => _to_uri ($self->{path} . '/statements', $w, {}) ); |
281
|
0
|
|
|
|
|
|
my $resp = $ua->request ($requ); |
282
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
} |
285
|
0
|
|
|
|
|
|
default { die "You should never end here: Unhandled '$method'"; } |
|
0
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
} |
288
|
0
|
0
|
|
|
|
|
if ($n3) { # if we have something to say to the server |
289
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new ($method => $self->{path} . '/statements', [ 'Content-Type' => 'text/plain' ], $n3); |
290
|
0
|
|
|
|
|
|
my $resp = $ua->request ($requ); |
291
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
292
|
|
|
|
|
|
|
} |
293
|
0
|
|
|
|
|
|
for my $file (@files) { # if we have something to say to the server |
294
|
15
|
|
|
15
|
|
19535
|
use LWP::Simple; |
|
15
|
|
|
|
|
34
|
|
|
15
|
|
|
|
|
162
|
|
295
|
0
|
0
|
|
|
|
|
my $content = get ($file) or die "Could not open URL '$file'"; |
296
|
0
|
|
|
|
|
|
my $mime; # lets guess the mime type |
297
|
0
|
|
|
|
|
|
given ($file) { # magic does not normally cope well with RDF/N3, so do it by extension |
298
|
0
|
|
|
|
|
|
when (/\.n3$/) { $mime = 'text/plain'; } # well, not really, since its text/n3 |
|
0
|
|
|
|
|
|
|
299
|
0
|
|
|
|
|
|
when (/\.nt$/) { $mime = 'text/plain'; } |
|
0
|
|
|
|
|
|
|
300
|
0
|
|
|
|
|
|
when (/\.xml$/) { $mime = 'application/rdf+xml'; } |
|
0
|
|
|
|
|
|
|
301
|
0
|
|
|
|
|
|
when (/\.rdf$/) { $mime = 'application/rdf+xml'; } |
|
0
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
|
default { die; } |
|
0
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
|
305
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new ($method => $self->{path} . '/statements', [ 'Content-Type' => $mime ], $content); |
306
|
0
|
|
|
|
|
|
my $resp = $ua->request ($requ); |
307
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
308
|
|
|
|
|
|
|
|
309
|
0
|
|
|
|
|
|
$method = 'POST'; # whatever the first was, the others must add to it! |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub _to_uri { |
316
|
0
|
|
|
0
|
|
|
my $path = shift; |
317
|
0
|
|
|
|
|
|
my $w = shift; |
318
|
0
|
|
|
|
|
|
my $options = shift; |
319
|
|
|
|
|
|
|
|
320
|
0
|
|
|
|
|
|
my $url = new URI ($path); |
321
|
0
|
0
|
|
|
|
|
$url->query_form ((defined $w->[0] |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
322
|
|
|
|
|
|
|
? ('subj' => $w->[0]) |
323
|
|
|
|
|
|
|
: () ), |
324
|
|
|
|
|
|
|
(defined $w->[1] |
325
|
|
|
|
|
|
|
? ('pred' => $w->[1]) |
326
|
|
|
|
|
|
|
: () ), |
327
|
|
|
|
|
|
|
(ref ($w->[2]) eq 'ARRAY' |
328
|
|
|
|
|
|
|
? ('obj' => $w->[2]->[0], |
329
|
|
|
|
|
|
|
'objEnd' => $w->[2]->[1] |
330
|
|
|
|
|
|
|
) |
331
|
|
|
|
|
|
|
: (defined $w->[2] |
332
|
|
|
|
|
|
|
? ('obj' => $w->[2]) |
333
|
|
|
|
|
|
|
: () |
334
|
|
|
|
|
|
|
)), |
335
|
|
|
|
|
|
|
(defined $options->{limit} |
336
|
|
|
|
|
|
|
? (limit => $options->{limit}) |
337
|
|
|
|
|
|
|
: ()) |
338
|
|
|
|
|
|
|
); |
339
|
0
|
|
|
|
|
|
return $url; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
=pod |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
=item B |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
This method behaves exactly like C, except that any existing content in the repository is wiped |
347
|
|
|
|
|
|
|
before adding anything. |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=cut |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
sub replace { |
352
|
0
|
|
|
0
|
1
|
|
_put_post_stmts ('PUT', @_); |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=pod |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=item B |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
I<$repo>->delete ([ I<$subj_uri>, I<$pred_uri>, I<$obj_uri> ], ...) |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
This method removes the passed in triples from the repository. In that process, any combination of |
362
|
|
|
|
|
|
|
the subject URI, the predicate or the object URI can be left C. That is interpreted as |
363
|
|
|
|
|
|
|
wildcard which matches anything. |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
Example: This deletes anything where the Stephansdom is the subject: |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
$air->delete ([ '', undef, undef ]) |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=cut |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
sub delete { |
372
|
0
|
|
|
0
|
1
|
|
_put_post_stmts ('DELETE', @_); |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=pod |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=item B |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
I<@stmts> = I<$repo>->match ([ I<$subj_uri>, I<$pred_uri>, I<$obj_uri> ], ...) |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
This method returns a list of all statements which match one of the triples provided |
382
|
|
|
|
|
|
|
as parameter. Any C as URI within such a triple is interpreted as wildcard, matching |
383
|
|
|
|
|
|
|
any other URI. |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
(Since v0.06): The object part can now be a range of values. You simply provide an array reference |
386
|
|
|
|
|
|
|
with the lower and the upper bound as values in the array, such as for example |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
$repo->match ([ undef, undef, [ '"1"^^my:type', '"10"^^my:type' ] ]); |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
B: Subject range queries and predicate range queries are not supported as RDF would not allow |
391
|
|
|
|
|
|
|
literals at these places anyway. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
(Since v0.06): For AGv4 there is now a way to configure some options when fetching matching triples: |
394
|
|
|
|
|
|
|
Simply provide as first parameter an options hash: |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
$repo->match ({ limit => 10 }, [ undef, .....]); |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
These options will apply to all passed in match patterns SEPARATELY, so that with several patterns |
399
|
|
|
|
|
|
|
you might well get more than your limit. |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=cut |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
sub match { |
404
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
405
|
0
|
0
|
|
|
|
|
my $options = ref($_[0]) eq 'HASH' ? shift : {}; |
406
|
|
|
|
|
|
|
|
407
|
0
|
|
|
|
|
|
my @stmts; |
408
|
0
|
|
|
|
|
|
my $ua = $self->{CATALOG}->{SERVER}->{ua}; |
409
|
0
|
|
|
|
|
|
foreach my $w (@_) { |
410
|
0
|
|
|
|
|
|
my $resp = $ua->get (_to_uri ($self->{path} . '/statements', $w, $options)); |
411
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
412
|
0
|
|
|
|
|
|
push @stmts, @{ from_json ($resp->content) }; |
|
0
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
} |
414
|
0
|
|
|
|
|
|
return @stmts; |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
sub _query { |
418
|
0
|
|
|
0
|
|
|
my $self = shift; |
419
|
0
|
|
|
|
|
|
my $query = shift; |
420
|
0
|
|
0
|
|
|
|
my $lang = shift || 'sparql'; |
421
|
0
|
|
|
|
|
|
my %options = @_; |
422
|
|
|
|
|
|
|
|
423
|
0
|
|
0
|
|
|
|
$options{RETURN} ||= 'TUPLE_LIST'; # a good default |
424
|
0
|
|
|
|
|
|
my $NAMED = 0; |
425
|
0
|
0
|
|
|
|
|
($NAMED, $options{RETURN}) = (1, 'TUPLE_LIST') if $options{RETURN} eq 'NAMED_TUPLE_LIST'; # store the info that we should return the names as well |
426
|
|
|
|
|
|
|
|
427
|
0
|
|
|
|
|
|
my @params; |
428
|
0
|
|
|
|
|
|
push @params, "queryLn=$lang"; |
429
|
0
|
|
|
|
|
|
push @params, 'query='.uri_escape_utf8 ($query); |
430
|
0
|
0
|
|
|
|
|
push @params, 'infer='.uri_escape_utf8 ($options{INFERENCING}) if defined $options{INFERENCING}; |
431
|
|
|
|
|
|
|
|
432
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($self->{path} . '?' . join ('&', @params) ); |
433
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
434
|
|
|
|
|
|
|
|
435
|
0
|
|
|
|
|
|
my $json = from_json ($resp->content); |
436
|
0
|
|
|
|
|
|
given ($options{RETURN}) { |
437
|
0
|
|
|
|
|
|
when ('TUPLE_LIST') { |
438
|
0
|
0
|
|
|
|
|
return $NAMED ? ($json) : @{ $json->{values} }; |
|
0
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
} |
440
|
0
|
|
|
|
|
|
default { die }; |
|
0
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=pod |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=item B |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
I<@tuples> = I<$repo>->sparql ('SELECT ...') |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
I<@tuples> = I<$repo>->sparql ('SELECT ...' [, I<$option> => I<$value> ]) |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
This method takes a SPARQL query string and returns a list of tuples which the query produced from |
453
|
|
|
|
|
|
|
the repository. |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
B: At the moment only SELECT queries are supported. |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
As additional options are accepted: |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=over |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=item C (default: C) |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
In the case of C the result will be a sequence of (references to) arrays. All naming of |
464
|
|
|
|
|
|
|
the individual columns is hereby lost. C really only returns the data (and not the names |
465
|
|
|
|
|
|
|
within SELECT clause). |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
(since v0.08) |
468
|
|
|
|
|
|
|
C also returns a hash with the names (list reference) and the result sequence |
469
|
|
|
|
|
|
|
(list reference, too). |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=item C (default: undef) |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
[Since v0.08] With this option you can control the degree of inferencing used with this query. |
474
|
|
|
|
|
|
|
By default, no inferencing is used, but if you pass in C, then the semantics of those |
475
|
|
|
|
|
|
|
properties mentioned in C<.../doc/agraph-introduction.html#reasoning> are honored. |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=back |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=cut |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
sub sparql { |
482
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
483
|
0
|
|
|
|
|
|
my $query = shift; |
484
|
|
|
|
|
|
|
|
485
|
0
|
|
|
|
|
|
return _query ($self, $query, 'sparql', @_); |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=pod |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
=item B (since v0.06) |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
See C, but this is only supported for AGv4 servers. |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=cut |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
sub prolog { |
497
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
498
|
0
|
|
|
|
|
|
my $query = shift; |
499
|
|
|
|
|
|
|
|
500
|
0
|
|
|
|
|
|
return _query ($self, $query, 'prolog', @_); |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
=pod |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=back |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=head2 Namespace Support |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=over |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=item B |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
I<%ns> = I<$repo>->namespaces |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
This read-only function returns a hash containing the namespaces: keys |
517
|
|
|
|
|
|
|
are the prefixes, values are the namespace URIs. |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
B: No AllegroGraph I is honored at the moment. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
B: My current understanding is that AG does NOT support namespaces when you load data with |
522
|
|
|
|
|
|
|
C or C, or try to match it with C. In that case, all URIs must be fully |
523
|
|
|
|
|
|
|
expanded. Namespaces seem to work with SPARQL queries, though. |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
=cut |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
sub namespaces { |
528
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
529
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($self->{path} . '/namespaces'); |
530
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
531
|
|
|
|
|
|
|
return |
532
|
0
|
|
|
|
|
|
map { $_->{prefix} => $_->{namespace} } |
|
0
|
|
|
|
|
|
|
533
|
0
|
|
|
|
|
|
@{ from_json ($resp->content) }; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
=pod |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
=item B |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
$uri = $repo->namespace ($prefix) |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
$uri = $repo->namespace ($prefix => $uri) |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
$repo->namespace ($prefix => undef) |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
This method fetches, sets and deletes prefix/uri namespaces. If only the prefix is given, |
547
|
|
|
|
|
|
|
it will look up the namespace URI. If the URI is provided as second parameter, it will set/overwrite |
548
|
|
|
|
|
|
|
that prefix. If the second parameter is C, it will delete the namespace associated with it. |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
B: No I is honored at the moment. |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
=cut |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
sub namespace { |
555
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
556
|
0
|
|
|
|
|
|
my $prefix = shift; |
557
|
|
|
|
|
|
|
|
558
|
0
|
|
|
|
|
|
my $uri = $self->{path} . '/namespaces/' . $prefix; |
559
|
0
|
0
|
|
|
|
|
if (scalar @_) { # there was a second argument! |
560
|
0
|
0
|
|
|
|
|
if (my $nsuri = shift) { |
561
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new ('PUT' => $uri, [ 'Content-Type' => 'text/plain' ], $nsuri); |
562
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ); |
563
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
564
|
0
|
|
|
|
|
|
return $nsuri; |
565
|
|
|
|
|
|
|
} else { |
566
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new ('DELETE' => $uri); |
567
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ); |
568
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
} else { |
571
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($uri); |
572
|
0
|
0
|
|
|
|
|
return undef if $resp->code == 404; |
573
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
574
|
0
|
|
0
|
|
|
|
return $resp->content =~ m/^"?(.*?)"?$/ && $1; |
575
|
|
|
|
|
|
|
} |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
=pod |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=back |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=head2 GeoSpatial Support |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
=over |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=item B |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
I<@geotypes> = I<$repo>->geotypes |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
This method returns a list of existing geotypes (in form of specially |
591
|
|
|
|
|
|
|
crafted URIs). You need these URIs when you want to create locations |
592
|
|
|
|
|
|
|
for them, or when you want to retrieve tuples within a specific area |
593
|
|
|
|
|
|
|
(based on the geotype). |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=cut |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
sub geotypes { |
598
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
599
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->get ($self->{path} . '/geo/types'); |
600
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
601
|
0
|
|
|
|
|
|
return @{ from_json ($resp->content) }; |
|
0
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
} |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=pod |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=item B |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
I<$coord> = I<$repo>->spherical (C, '5.2 degree'); |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
This method registers a spherical coordinate system on the server. |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
B: With this version, no region can be specified (so this is always a complete sphere) and |
613
|
|
|
|
|
|
|
only degrees are supported. |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
=cut |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
sub spherical { |
618
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
619
|
0
|
|
|
|
|
|
my $region = shift; |
620
|
0
|
|
|
|
|
|
my $scale = shift; |
621
|
|
|
|
|
|
|
|
622
|
15
|
|
|
15
|
|
55127
|
use Regexp::Common; |
|
15
|
|
|
|
|
44
|
|
|
15
|
|
|
|
|
119
|
|
623
|
0
|
0
|
|
|
|
|
die "scale information must be of the form 5 mile, or 10 km, or similar" |
624
|
|
|
|
|
|
|
unless ($scale =~ /($RE{num}{real})(\s+(degree|mile|km|radian))?/); |
625
|
|
|
|
|
|
|
|
626
|
0
|
|
|
|
|
|
my $stripW = $1; |
627
|
0
|
0
|
|
|
|
|
my $unit = $3 if $2; # leave it undef otherwise |
628
|
|
|
|
|
|
|
|
629
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/geo/types/spherical'); |
630
|
0
|
0
|
|
|
|
|
$url->query_form (stripWidth => $stripW, |
631
|
|
|
|
|
|
|
($unit |
632
|
|
|
|
|
|
|
? (unit => $unit) # be explicit |
633
|
|
|
|
|
|
|
: () |
634
|
|
|
|
|
|
|
) |
635
|
|
|
|
|
|
|
); |
636
|
|
|
|
|
|
|
|
637
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (POST $url); |
638
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
639
|
0
|
|
0
|
|
|
|
return $resp->content =~ m/^"?(.*?)"?$/ && $1; |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=pod |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=item B |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
I<$uri> = I<$repo>->cartesian ("100x100", I<$stripWidth>); |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
I<$uri> = I<$repo>->cartesian ("100x100+10+10", I<$stripWidth>); |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
I<$uri> = I<$repo>->cartesian (I<$minx>, I<$miny>, I<$maxx>, I<$maxy>, I<$stripWidth>); |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
This method registers one new coordinate system at the server. The returned URI is later used as |
653
|
|
|
|
|
|
|
reference to that system. The extensions of the system is provided either |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
=over |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
=item in the form C |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
All numbers being floats. The X,Y offset part can be omitted. |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
=item or, alternatively, as minx, miny, maxx, maxy quadruple |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
Again all numbers being floats. |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
=back |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
The last parameter defines the resolution of the stripes, and gives the server optimization hints. |
668
|
|
|
|
|
|
|
(See the general AG description for a deep explanation.) |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
=cut |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
sub cartesian { |
673
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
674
|
|
|
|
|
|
|
|
675
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/geo/types/cartesian'); |
676
|
|
|
|
|
|
|
|
677
|
15
|
|
|
15
|
|
84793
|
use Regexp::Common; |
|
15
|
|
|
|
|
43
|
|
|
15
|
|
|
|
|
73
|
|
678
|
0
|
0
|
|
|
|
|
if ($_[0] =~ /($RE{num}{real})x($RE{num}{real})(\+($RE{num}{real})\+($RE{num}{real}))?/) { |
679
|
0
|
|
|
|
|
|
shift; |
680
|
0
|
|
0
|
|
|
|
my ($W, $H, $X, $Y) = ($1, $2, $4||0, $5||0); |
|
|
|
0
|
|
|
|
|
681
|
0
|
|
|
|
|
|
my $stripW = shift; |
682
|
0
|
|
|
|
|
|
$url->query_form (stripWidth => $stripW, xmin => $X, xmax => $X+$W, ymin => $Y, ymax => $Y+$H); |
683
|
|
|
|
|
|
|
} else { |
684
|
0
|
|
|
|
|
|
my ($X1, $Y1, $X2, $Y2, $stripW) = @_; |
685
|
0
|
|
|
|
|
|
$url->query_form (stripWidth => $stripW, xmin => $X1, xmax => $X2, ymin => $Y1, ymax => $Y2); |
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
|
688
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (POST $url); |
689
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
690
|
0
|
|
0
|
|
|
|
return $resp->content =~ m/^"?(.*?)"?$/ && $1; |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
=pod |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
=item B |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
I<@ss> = I<$repo>->inBox (I<$geotype>, I<$predicate>, 35, 35, 65, 65, { limit => 10 }); |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
This method tries to find all triples which lie within a certain bounding box. |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
The geotype is the one you create with C or C. The bounding box is given by the |
702
|
|
|
|
|
|
|
bottom/left and the top/right corner coordinates. The optional C restricts the number of |
703
|
|
|
|
|
|
|
triples you request. |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
For cartesian coordinates you provide the bottom/left corner, and then the top/right one. |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
For spherical coordinates you provide the longitude/latitude of the bottom/left corner, then |
708
|
|
|
|
|
|
|
the longitude/latitude of the top/right one. |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
=cut |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
sub inBox { |
713
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
714
|
0
|
|
|
|
|
|
my $geotype = shift; |
715
|
0
|
|
|
|
|
|
my $pred = shift; |
716
|
0
|
|
|
|
|
|
my ($xmin, $ymin, $xmax, $ymax) = @_; |
717
|
0
|
|
0
|
|
|
|
my $options = $_[4] || {}; |
718
|
|
|
|
|
|
|
|
719
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/geo/box'); |
720
|
0
|
0
|
|
|
|
|
$url->query_form (type => $geotype, |
721
|
|
|
|
|
|
|
predicate => $pred, |
722
|
|
|
|
|
|
|
xmin => $xmin, |
723
|
|
|
|
|
|
|
ymin => $ymin, |
724
|
|
|
|
|
|
|
xmax => $xmax, |
725
|
|
|
|
|
|
|
ymax => $ymax, |
726
|
|
|
|
|
|
|
(defined $options->{limit} |
727
|
|
|
|
|
|
|
? (limit => $options->{limit}) |
728
|
|
|
|
|
|
|
: ()) |
729
|
|
|
|
|
|
|
); |
730
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url); |
731
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
732
|
0
|
|
|
|
|
|
return @{ from_json ($resp->content) }; |
|
0
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
=pod |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
=item B |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
I<@ss> = I<$repo>->inCircle (I<$geotype>, I<$predicate>, 35, 35, 10, { limit => 10 }); |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
This method tries to find all triples which lie within a certain bounding circle. |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
The geotype is the one you create with C or C. The bounding circle is given by |
744
|
|
|
|
|
|
|
the center and the radius. The optional C restricts the number of triples you request. |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
For cartesian coordinates you simply provide the X/Y coordinates of the circle center, and the |
747
|
|
|
|
|
|
|
radius (in the unit as provided with the geotype. |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
For spherical coordinates the center is specified with a longitude/latitude pair. The radius is also |
750
|
|
|
|
|
|
|
interpreted along the provided geotype. |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
B: As it seems, the circle MUST be totally within the range you specified for your |
753
|
|
|
|
|
|
|
geotype. Otherwise AG will return 0 tuples. |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
=cut |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
sub inCircle { |
758
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
759
|
0
|
|
|
|
|
|
my $geotype = shift; |
760
|
0
|
|
|
|
|
|
my $pred = shift; |
761
|
0
|
|
|
|
|
|
my ($x, $y, $radius) = @_; |
762
|
0
|
|
0
|
|
|
|
my $options = $_[3] || {}; |
763
|
|
|
|
|
|
|
|
764
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/geo/circle'); |
765
|
0
|
0
|
|
|
|
|
$url->query_form (type => $geotype, |
766
|
|
|
|
|
|
|
predicate => $pred, |
767
|
|
|
|
|
|
|
x => $x, |
768
|
|
|
|
|
|
|
y => $y, |
769
|
|
|
|
|
|
|
radius => $radius, |
770
|
|
|
|
|
|
|
(defined $options->{limit} |
771
|
|
|
|
|
|
|
? (limit => $options->{limit}) |
772
|
|
|
|
|
|
|
: ()) |
773
|
|
|
|
|
|
|
); |
774
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url); |
775
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
776
|
0
|
|
|
|
|
|
return @{ from_json ($resp->content) }; |
|
0
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
=pod |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
=item I (since v0.06) |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
I<@ss> = I<$repo>->inPolygon (I<$coordtype>, I<$preduri>, I<@points>, { I<%options> }) |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
This method tries to identify all statements where the object is within a polygon defined by the |
786
|
|
|
|
|
|
|
C array. Each point is simply an array reference with 2 entries (x,y, of course). |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
The predicate URI defines which predicates should be considered. Do not leave it C. The |
789
|
|
|
|
|
|
|
coordinate type is the one you will have generated before with C. |
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
The optional options can only contain C to restrict the number of tuples to be returned. |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
For spherical coordinates make sure that you (a) provide longitude/latitude pairs and then that the |
794
|
|
|
|
|
|
|
polygon is built clockwise. |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
B: This is a somewhat expensive operation in terms of communication round-trips. |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
=cut |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
sub inPolygon { |
801
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
802
|
0
|
|
|
|
|
|
my $geotype = shift; |
803
|
0
|
|
|
|
|
|
my $pred = shift; |
804
|
0
|
|
|
|
|
|
my @points; |
805
|
0
|
|
|
|
|
|
while (ref($_[0]) eq 'ARRAY') { |
806
|
15
|
|
|
15
|
|
25986
|
use RDF::AllegroGraph::Utils qw(coord2literal); |
|
15
|
|
|
|
|
35
|
|
|
15
|
|
|
|
|
22066
|
|
807
|
0
|
|
|
|
|
|
push @points, coord2literal ($geotype, @{ shift @_ }); |
|
0
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
} |
809
|
0
|
|
0
|
|
|
|
my $options = shift || {}; |
810
|
|
|
|
|
|
|
|
811
|
0
|
|
|
|
|
|
my ($blank) = $self->blanks; # get one blank node |
812
|
|
|
|
|
|
|
|
813
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/geo/polygon'); # build request to park polygon temporarily |
814
|
0
|
|
|
|
|
|
$url->query_form (resource => $blank, # under the blank node |
815
|
|
|
|
|
|
|
point => \@points # with these points expanded |
816
|
|
|
|
|
|
|
); |
817
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (PUT $url); # AGv4 does seem to require to have that URL encoded (with PUT??) |
818
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
819
|
|
|
|
|
|
|
|
820
|
0
|
|
|
|
|
|
$url = new URI ($self->{path} . '/geo/polygon'); # build request to park polygon temporarily |
821
|
0
|
0
|
|
|
|
|
$url->query_form (polygon => $blank, # under the blank node |
822
|
|
|
|
|
|
|
type => $geotype, # for this geotype |
823
|
|
|
|
|
|
|
predicate => $pred, # and for this predicate |
824
|
|
|
|
|
|
|
(defined $options->{limit} |
825
|
|
|
|
|
|
|
? (limit => $options->{limit}) |
826
|
|
|
|
|
|
|
: ()) |
827
|
|
|
|
|
|
|
); |
828
|
0
|
|
|
|
|
|
$resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url); # now we make the real query |
829
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
830
|
0
|
|
|
|
|
|
return @{ from_json ($resp->content) }; |
|
0
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
} |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
=pod |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
=item B |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
This method will return a list of indices which the repository on the server understands. The list |
839
|
|
|
|
|
|
|
contains strings of the form C which identify the bias of the index. See |
840
|
|
|
|
|
|
|
L |
841
|
|
|
|
|
|
|
for some introduction. |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
B: These are NOT the indices which are active for that repository. See C for that. |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
=cut |
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
sub valid_indices { |
848
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
849
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/indices?listValid=true'); |
850
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url); |
851
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
852
|
0
|
|
|
|
|
|
return @{ from_json ($resp->content) }; |
|
0
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
=pod |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
=item B |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
I<@idxs> = I<$rep>->indices ([ I ]) |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
This method always returns the current list of applied indices for that repository. |
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
Optionally you can pass in a list of changes you want in terms of indices, I in terms of |
864
|
|
|
|
|
|
|
indices you want to add, or to remove. To add, say, a C index you would prefix it with a '+': |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
$rep->indices ('+spogi') |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
You can provide any number of such additions. In the same way you would a prefixed '-' to indicate |
869
|
|
|
|
|
|
|
that you want an index to be deleted. |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
=cut |
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
sub indices { |
874
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
875
|
0
|
|
|
|
|
|
foreach my $sidx (@_) { # in the case we want changes to be made |
876
|
0
|
0
|
|
|
|
|
if ($sidx =~ m{\-(.+)}) { # removal of indices |
|
|
0
|
|
|
|
|
|
877
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/indices/' . $1); |
878
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new (DELETE => $url); |
879
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ); |
880
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
} elsif ($sidx =~ m{\+(.+)}) { # adding of indices |
883
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/indices/' . $1); |
884
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new (PUT => $url); |
885
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request ($requ); |
886
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
} else { # not sure what this is => ignorance is bliss |
889
|
0
|
|
|
|
|
|
warn "not sure what to do with '$sidx', ignoring ..."; |
890
|
|
|
|
|
|
|
} |
891
|
|
|
|
|
|
|
} |
892
|
|
|
|
|
|
|
# now collect the state of affairs from the server |
893
|
0
|
|
|
|
|
|
my $url = new URI ($self->{path} . '/indices'); |
894
|
0
|
|
|
|
|
|
my $resp = $self->{CATALOG}->{SERVER}->{ua}->request (GET $url); |
895
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
896
|
0
|
|
|
|
|
|
return @{ from_json ($resp->content) }; |
|
0
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
} |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
=pod |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
=item B |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
I<$bool> = I<$repo>->bulk_loading_mode (C<1|0>) |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
This method switches on and off the bulk loading capability of the repository. To enable it, pass in |
907
|
|
|
|
|
|
|
C<1>, to turn it off pass in C<0>. In any case the current state is returned where C is |
908
|
|
|
|
|
|
|
returned instead of C<0>. |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
=cut |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
sub _mode { |
913
|
0
|
|
|
0
|
|
|
my $ua = shift; |
914
|
0
|
|
|
|
|
|
my $path = shift; |
915
|
0
|
|
|
|
|
|
my $val = shift; |
916
|
|
|
|
|
|
|
|
917
|
0
|
0
|
|
|
|
|
if (defined $val) { |
918
|
0
|
0
|
|
|
|
|
if ($val) { |
919
|
0
|
|
|
|
|
|
my $resp = $ua->request (PUT $path, 'Content' => $val); |
920
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
921
|
|
|
|
|
|
|
} else { |
922
|
0
|
|
|
|
|
|
my $requ = HTTP::Request->new (DELETE => $path); |
923
|
0
|
|
|
|
|
|
my $resp = $ua->request ($requ); |
924
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
925
|
|
|
|
|
|
|
} |
926
|
|
|
|
|
|
|
} |
927
|
0
|
|
|
|
|
|
my $resp = $ua->get ($path); |
928
|
0
|
0
|
|
|
|
|
die "protocol error: ".$resp->status_line.' ('.$resp->content.')' unless $resp->is_success; |
929
|
0
|
|
|
|
|
|
return $resp->content eq 'true'; |
930
|
|
|
|
|
|
|
} |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
sub bulk_loading_mode { |
933
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
934
|
0
|
|
|
|
|
|
my $val = shift; |
935
|
|
|
|
|
|
|
|
936
|
0
|
|
|
|
|
|
return _mode ($self->{CATALOG}->{SERVER}->{ua}, $self->{path} . '/bulkMode', $val); |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
} |
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
=pod |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
=item B |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
I<$bool> = I<$repo>->commit_mode (C<1|0>) |
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
Method to control the commit mode of a repository. Parameters and return values are like those for C. |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
=cut |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
sub commit_mode { |
951
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
952
|
0
|
|
|
|
|
|
my $val = shift; |
953
|
|
|
|
|
|
|
|
954
|
0
|
0
|
|
|
|
|
return ! _mode ($self->{CATALOG}->{SERVER}->{ua}, $self->{path} . '/noCommit', defined $val ? abs($val-1) : undef); |
955
|
|
|
|
|
|
|
} |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
=pod |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
=item B |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
I<$bool> = I<$repo>->duplicate_suppression_mode (C<1|0>) |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
Method to control the duplicate suppression behavior of a repository. Parameters and return values |
964
|
|
|
|
|
|
|
are like those for C. |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
=cut |
967
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
sub duplicate_suppression_mode { |
969
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
970
|
0
|
|
|
|
|
|
my $val = shift; |
971
|
|
|
|
|
|
|
|
972
|
0
|
|
|
|
|
|
return _mode ($self->{CATALOG}->{SERVER}->{ua}, $self->{path} . '/deleteDuplicates', $val); |
973
|
|
|
|
|
|
|
} |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
=pod |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
=back |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
=cut |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
our $VERSION = '0.07'; |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
1; |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
__END__ |