line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=head1 NAME |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
Attean - A Semantic Web Framework |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 VERSION |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
This document describes Attean version 0.033 |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 SYNOPSIS |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
use Attean; |
12
|
|
|
|
|
|
|
use Attean::RDF qw(iri); |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
my $store = Attean->get_store('Memory')->new(); |
15
|
|
|
|
|
|
|
my $parser = Attean->get_parser('NTriples')->new(); |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# iterator of triples and quads |
18
|
|
|
|
|
|
|
my $iter = $parser->parse_iter_from_io(\*STDIN); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# add a graph name to all triples |
21
|
|
|
|
|
|
|
my $graph = iri('http://graph-name/'); |
22
|
|
|
|
|
|
|
my $quads = $iter->as_quads($graph); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
$store->add_iter($quads); |
25
|
|
|
|
|
|
|
my $model = Attean::QuadModel->new( store => $store ); |
26
|
|
|
|
|
|
|
my $iter = $model->get_quads(); |
27
|
|
|
|
|
|
|
while (my $quad = $iter->next) { |
28
|
|
|
|
|
|
|
say $quad->object->ntriples_string; |
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# run a SPARQL query and iterate over the results |
32
|
|
|
|
|
|
|
my $sparql = 'SELECT * WHERE { ?s ?p ?o }'; |
33
|
|
|
|
|
|
|
my $s = Attean->get_parser('SPARQL')->new(); |
34
|
|
|
|
|
|
|
my ($algebra) = $s->parse($sparql); |
35
|
|
|
|
|
|
|
my $results = $model->evaluate($algebra, $graph); |
36
|
|
|
|
|
|
|
while (my $r = $results->next) { |
37
|
|
|
|
|
|
|
say $r->as_string; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=head1 DESCRIPTION |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
Attean provides APIs for parsing, storing, querying, and serializing |
43
|
|
|
|
|
|
|
Semantic Web (RDF and SPARQL) data. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 METHODS |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=over 4 |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=cut |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
use v5.14; |
52
|
50
|
|
|
50
|
|
6319147
|
use warnings; |
|
50
|
|
|
|
|
408
|
|
53
|
50
|
|
|
50
|
|
216
|
our $VERSION = '0.033'; |
|
50
|
|
|
|
|
74
|
|
|
50
|
|
|
|
|
1631
|
|
54
|
|
|
|
|
|
|
use Attean::API; |
55
|
50
|
|
|
50
|
|
16737
|
|
|
50
|
|
|
|
|
137
|
|
|
50
|
|
|
|
|
2257
|
|
56
|
|
|
|
|
|
|
use Attean::Blank; |
57
|
50
|
|
|
50
|
|
392
|
use Attean::Literal; |
|
50
|
|
|
|
|
92
|
|
|
50
|
|
|
|
|
1025
|
|
58
|
50
|
|
|
50
|
|
21163
|
use Attean::Variable; |
|
50
|
|
|
|
|
147
|
|
|
50
|
|
|
|
|
1574
|
|
59
|
50
|
|
|
50
|
|
365
|
use Attean::IRI; |
|
50
|
|
|
|
|
111
|
|
|
50
|
|
|
|
|
897
|
|
60
|
50
|
|
|
50
|
|
223
|
|
|
50
|
|
|
|
|
88
|
|
|
50
|
|
|
|
|
1148
|
|
61
|
|
|
|
|
|
|
use Attean::Triple; |
62
|
50
|
|
|
50
|
|
20374
|
use Attean::Quad; |
|
50
|
|
|
|
|
154
|
|
|
50
|
|
|
|
|
1516
|
|
63
|
50
|
|
|
50
|
|
19616
|
use Attean::Result; |
|
50
|
|
|
|
|
145
|
|
|
50
|
|
|
|
|
1758
|
|
64
|
50
|
|
|
50
|
|
19871
|
|
|
50
|
|
|
|
|
146
|
|
|
50
|
|
|
|
|
1739
|
|
65
|
|
|
|
|
|
|
use Attean::QuadModel; |
66
|
50
|
|
|
50
|
|
18288
|
use Attean::TripleModel; |
|
50
|
|
|
|
|
169
|
|
|
50
|
|
|
|
|
1671
|
|
67
|
50
|
|
|
50
|
|
22091
|
use Attean::BindingEqualityTest; |
|
50
|
|
|
|
|
209
|
|
|
50
|
|
|
|
|
2147
|
|
68
|
50
|
|
|
50
|
|
21444
|
|
|
50
|
|
|
|
|
148
|
|
|
50
|
|
|
|
|
1488
|
|
69
|
|
|
|
|
|
|
use Attean::CodeIterator; |
70
|
50
|
|
|
50
|
|
20500
|
use Attean::ListIterator; |
|
50
|
|
|
|
|
147
|
|
|
50
|
|
|
|
|
1576
|
|
71
|
50
|
|
|
50
|
|
359
|
use Attean::IteratorSequence; |
|
50
|
|
|
|
|
94
|
|
|
50
|
|
|
|
|
1370
|
|
72
|
50
|
|
|
50
|
|
19816
|
|
|
50
|
|
|
|
|
159
|
|
|
50
|
|
|
|
|
1792
|
|
73
|
|
|
|
|
|
|
use Attean::IDPQueryPlanner; |
74
|
50
|
|
|
50
|
|
18830
|
|
|
50
|
|
|
|
|
178
|
|
|
50
|
|
|
|
|
1620
|
|
75
|
|
|
|
|
|
|
use Attean::TermMap; |
76
|
50
|
|
|
50
|
|
20947
|
|
|
50
|
|
|
|
|
163
|
|
|
50
|
|
|
|
|
1594
|
|
77
|
|
|
|
|
|
|
use HTTP::Negotiate qw(choose); |
78
|
50
|
|
|
50
|
|
101588
|
use List::MoreUtils qw(any all); |
|
50
|
|
|
|
|
2166
|
|
|
50
|
|
|
|
|
3492
|
|
79
|
50
|
|
|
50
|
|
356
|
use Module::Load::Conditional qw(can_load); |
|
50
|
|
|
|
|
110
|
|
|
50
|
|
|
|
|
438
|
|
80
|
50
|
|
|
50
|
|
46567
|
use Role::Tiny (); |
|
50
|
|
|
|
|
132
|
|
|
50
|
|
|
|
|
1914
|
|
81
|
50
|
|
|
50
|
|
256
|
use Sub::Util qw(set_subname); |
|
50
|
|
|
|
|
106
|
|
|
50
|
|
|
|
|
752
|
|
82
|
50
|
|
|
50
|
|
217
|
use namespace::clean; |
|
50
|
|
|
|
|
96
|
|
|
50
|
|
|
|
|
1587
|
|
83
|
50
|
|
|
50
|
|
259
|
|
|
50
|
|
|
|
|
107
|
|
|
50
|
|
|
|
|
361
|
|
84
|
|
|
|
|
|
|
use Module::Pluggable search_path => 'AtteanX::Parser', sub_name => 'parsers', max_depth => 3; |
85
|
50
|
|
|
50
|
|
33951
|
use Module::Pluggable search_path => 'AtteanX::Serializer', sub_name => 'serializers', max_depth => 3; |
|
50
|
|
|
|
|
373104
|
|
|
50
|
|
|
|
|
361
|
|
86
|
50
|
|
|
50
|
|
4966
|
use Module::Pluggable search_path => 'AtteanX::Store', sub_name => 'stores', max_depth => 3; |
|
50
|
|
|
|
|
110
|
|
|
50
|
|
|
|
|
184
|
|
87
|
50
|
|
|
50
|
|
4191
|
|
|
50
|
|
|
|
|
119
|
|
|
50
|
|
|
|
|
223
|
|
88
|
|
|
|
|
|
|
my $class = shift; |
89
|
|
|
|
|
|
|
if (scalar(@_)) { |
90
|
138
|
|
|
138
|
|
571
|
my %args = @_; |
91
|
138
|
100
|
|
|
|
23564
|
foreach my $p (@{ $args{parsers} || [] }) { |
92
|
2
|
|
|
|
|
9
|
# warn "Loading $p parser..."; |
93
|
2
|
50
|
|
|
|
5
|
$class->get_parser($p) || die "Failed to load parser: $p"; |
|
2
|
|
|
|
|
19
|
|
94
|
|
|
|
|
|
|
} |
95
|
2
|
50
|
|
|
|
9
|
foreach my $s (@{ $args{serializers} || [] }) { |
96
|
|
|
|
|
|
|
# warn "Loading $s serializer..."; |
97
|
2
|
50
|
|
|
|
6
|
$class->get_serializer($s) || die "Failed to load serializer: $s"; |
|
2
|
|
|
|
|
16
|
|
98
|
|
|
|
|
|
|
} |
99
|
0
|
0
|
|
|
|
0
|
foreach my $s (@{ $args{stores} || [] }) { |
100
|
|
|
|
|
|
|
# warn "Loading $s store..."; |
101
|
2
|
50
|
|
|
|
6
|
$class->get_store($s) || die "Failed to load store: $s"; |
|
2
|
|
|
|
|
118
|
|
102
|
|
|
|
|
|
|
} |
103
|
0
|
0
|
|
|
|
0
|
} |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=item C<< get_store( $NAME ) >> |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Attempts to find a L<Attean::API::Store> implementation with the |
109
|
|
|
|
|
|
|
given C<< $NAME >>. This is done using L<Module::Pluggable> and will generally |
110
|
|
|
|
|
|
|
be searching for class names C<< AtteanX::Store::$NAME >>. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Returns the full class name if a matching implementation is found, otherwise |
113
|
|
|
|
|
|
|
returns undef. |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=cut |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
my $self = shift; |
118
|
|
|
|
|
|
|
return $self->_get_plugin('stores', shift); |
119
|
|
|
|
|
|
|
} |
120
|
76
|
|
|
76
|
1
|
49290
|
|
121
|
76
|
|
|
|
|
271
|
=item C<< temporary_model >> |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
Returns a temporary, mutable quad model based on a L<AtteanX::Store::Memory> store. |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=cut |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
my $self = shift; |
128
|
|
|
|
|
|
|
return Attean::MutableQuadModel->new( store => $self->get_store('Memory')->new() ) |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
3
|
|
|
3
|
1
|
714
|
|
132
|
3
|
|
|
|
|
18
|
|
133
|
|
|
|
|
|
|
=item C<< get_serializer( $NAME ) >> |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=item C<< get_serializer( filename => $FILENAME ) >> |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=item C<< get_serializer( media_type => $MEDIA_TYPE ) >> |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Attempts to find a L<Attean::API::Serializer> serializer class with the given |
140
|
|
|
|
|
|
|
C<< $NAME >>, or that can serialize files with the C<< $MEDIA_TYPE >> media |
141
|
|
|
|
|
|
|
type. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
Returns the full class name if a matching implementation is found, otherwise |
144
|
|
|
|
|
|
|
returns undef. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=cut |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
my $self = shift; |
149
|
|
|
|
|
|
|
my $role = 'Attean::API::Serializer'; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
if (scalar(@_) == 1) { |
152
|
|
|
|
|
|
|
my $name = shift; |
153
|
70
|
|
|
70
|
1
|
74851
|
my $p = $self->_get_plugin('serializers', $name, $role); |
154
|
70
|
|
|
|
|
127
|
return $p if $p; |
155
|
|
|
|
|
|
|
|
156
|
70
|
100
|
|
|
|
250
|
foreach my $type (qw'filename media_type') { |
157
|
49
|
|
|
|
|
116
|
my $p = $self->get_serializer($type => $name); |
158
|
49
|
|
|
|
|
183
|
return $p if $p; |
159
|
49
|
100
|
|
|
|
688
|
} |
160
|
|
|
|
|
|
|
return; |
161
|
17
|
|
|
|
|
47
|
} |
162
|
17
|
|
|
|
|
114
|
my $type = shift; |
163
|
17
|
50
|
|
|
|
196
|
my %method = (filename => 'file_extensions', media_type => 'media_types'); |
164
|
|
|
|
|
|
|
if (my $method = $method{ $type }) { |
165
|
0
|
|
|
|
|
0
|
my $value = shift; |
166
|
|
|
|
|
|
|
$value =~ s/^.*[.]// if ($type eq 'filename'); |
167
|
21
|
|
|
|
|
45
|
$value =~ s/;.*$// if ($type eq 'media_type'); |
168
|
21
|
|
|
|
|
102
|
foreach my $p ($self->serializers()) { |
169
|
21
|
100
|
|
|
|
92
|
if (can_load( modules => { $p => 0 })) { |
170
|
20
|
|
|
|
|
48
|
next unless ($p->does($role)); |
171
|
20
|
100
|
|
|
|
100
|
my @exts = @{ $p->$method() }; |
172
|
20
|
100
|
|
|
|
77
|
return $p if (any { $value eq $_ } @exts); |
173
|
20
|
|
|
|
|
98
|
} |
174
|
145
|
50
|
|
|
|
507271
|
} |
175
|
145
|
100
|
|
|
|
29703
|
return; |
176
|
129
|
|
|
|
|
2404
|
} else { |
|
129
|
|
|
|
|
617
|
|
177
|
129
|
100
|
|
170
|
|
723
|
die "Not a valid constraint in get_serializer call: $type"; |
|
170
|
|
|
|
|
720
|
|
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
} |
180
|
0
|
|
|
|
|
0
|
|
181
|
|
|
|
|
|
|
=item C<< get_parser( $NAME ) >> |
182
|
1
|
|
|
|
|
12
|
|
183
|
|
|
|
|
|
|
=item C<< get_parser( filename => $FILENAME ) >> |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=item C<< get_parser( media_type => $MEDIA_TYPE ) >> |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
Attempts to find a L<Attean::API::Parser> parser class with the given |
188
|
|
|
|
|
|
|
C<< $NAME >>, or that can parse files with the same extension as |
189
|
|
|
|
|
|
|
C<< $FILENAME >>, or that can parse files with the C<< $MEDIA_TYPE >> media |
190
|
|
|
|
|
|
|
type. |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
Returns the full class name if a matching implementation is found, otherwise |
193
|
|
|
|
|
|
|
returns undef. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=cut |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
my $self = shift; |
198
|
|
|
|
|
|
|
my $role = 'Attean::API::Parser'; |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
if (scalar(@_) == 1) { |
201
|
|
|
|
|
|
|
my $name = shift; |
202
|
|
|
|
|
|
|
my $p = $self->_get_plugin('parsers', $name, $role); |
203
|
190
|
|
|
190
|
1
|
200155
|
return $p if $p; |
204
|
190
|
|
|
|
|
405
|
|
205
|
|
|
|
|
|
|
foreach my $type (qw'filename media_type') { |
206
|
190
|
100
|
|
|
|
645
|
my $p = $self->get_parser($type => $name); |
207
|
175
|
|
|
|
|
300
|
return $p if $p; |
208
|
175
|
|
|
|
|
644
|
} |
209
|
175
|
100
|
|
|
|
3982
|
return; |
210
|
|
|
|
|
|
|
} |
211
|
11
|
|
|
|
|
27
|
|
212
|
11
|
|
|
|
|
88
|
while (my $type = shift) { |
213
|
11
|
50
|
|
|
|
125
|
my %method = (filename => 'file_extensions', media_type => 'media_types'); |
214
|
|
|
|
|
|
|
if (my $method = $method{ $type }) { |
215
|
0
|
|
|
|
|
0
|
my $value = shift; |
216
|
|
|
|
|
|
|
$value =~ s/^.*[.]// if ($type eq 'filename'); |
217
|
|
|
|
|
|
|
$value =~ s/;.*$// if ($type eq 'media_type'); |
218
|
15
|
|
|
|
|
63
|
foreach my $p ($self->parsers()) { |
219
|
15
|
|
|
|
|
72
|
if (can_load( modules => { $p => 0 })) { |
220
|
15
|
100
|
|
|
|
56
|
next unless ($p->can('does') and $p->does($role)); |
221
|
14
|
|
|
|
|
32
|
my @exts = @{ $p->$method() }; |
222
|
14
|
100
|
|
|
|
67
|
return $p if (any { $value eq $_ } @exts); |
223
|
14
|
100
|
|
|
|
50
|
} |
224
|
14
|
|
|
|
|
73
|
} |
225
|
73
|
50
|
|
|
|
1040663
|
} else { |
226
|
73
|
100
|
66
|
|
|
32754
|
die "Not a valid constraint in get_parser call: $type"; |
227
|
63
|
|
|
|
|
1363
|
} |
|
63
|
|
|
|
|
270
|
|
228
|
63
|
100
|
|
78
|
|
516
|
} |
|
78
|
|
|
|
|
406
|
|
229
|
|
|
|
|
|
|
return; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
1
|
|
|
|
|
13
|
{ |
233
|
|
|
|
|
|
|
my %roles = ( |
234
|
|
|
|
|
|
|
serializers => 'Attean::API::Serializer', |
235
|
0
|
|
|
|
|
0
|
parsers => 'Attean::API::Parser', |
236
|
|
|
|
|
|
|
stores => 'Attean::API::Store', |
237
|
|
|
|
|
|
|
); |
238
|
|
|
|
|
|
|
for my $method (keys %roles) { |
239
|
|
|
|
|
|
|
my $role = $roles{$method}; |
240
|
|
|
|
|
|
|
my $code = sub { |
241
|
|
|
|
|
|
|
my $self = shift; |
242
|
|
|
|
|
|
|
my @classes; |
243
|
|
|
|
|
|
|
foreach my $class ($self->$method()) { |
244
|
|
|
|
|
|
|
next unless (can_load( modules => { $class => 0 })); |
245
|
|
|
|
|
|
|
push(@classes, $class) if ($class->can('does') and $class->does($role)); |
246
|
|
|
|
|
|
|
} |
247
|
15
|
|
|
15
|
|
29
|
return @classes; |
|
|
|
|
15
|
|
|
|
|
|
|
|
15
|
|
|
|
248
|
15
|
|
|
|
|
30
|
}; |
249
|
15
|
|
|
|
|
96
|
Sub::Install::install_sub({ |
250
|
192
|
50
|
|
|
|
683177
|
code => set_subname("list_${method}", $code), |
251
|
192
|
100
|
66
|
|
|
42996
|
as => "list_${method}" |
252
|
|
|
|
|
|
|
}); |
253
|
15
|
|
|
|
|
315
|
} |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
my $self = shift; |
257
|
|
|
|
|
|
|
my $type = shift; |
258
|
|
|
|
|
|
|
my $name = shift; |
259
|
|
|
|
|
|
|
my @roles = @_; |
260
|
|
|
|
|
|
|
foreach my $p ($self->$type()) { |
261
|
|
|
|
|
|
|
if (lc(substr($p, -(length($name)+2))) eq lc("::$name")) { |
262
|
|
|
|
|
|
|
unless (can_load( modules => { $p => 0 })) { |
263
|
300
|
|
|
300
|
|
539
|
warn $Module::Load::Conditional::ERROR; |
264
|
300
|
|
|
|
|
480
|
return; |
265
|
300
|
|
|
|
|
553
|
} |
266
|
300
|
|
|
|
|
672
|
|
267
|
300
|
|
|
|
|
1630
|
foreach (@roles) { |
268
|
1896
|
100
|
|
|
|
12904232
|
unless ($p->does($_)) { |
269
|
272
|
50
|
|
|
|
1794
|
die ucfirst($type) . " class $p failed validation for role $_"; |
270
|
0
|
|
|
|
|
0
|
} |
271
|
0
|
|
|
|
|
0
|
} |
272
|
|
|
|
|
|
|
return $p; |
273
|
|
|
|
|
|
|
} |
274
|
272
|
|
|
|
|
69375
|
} |
275
|
196
|
50
|
|
|
|
1338
|
} |
276
|
0
|
|
|
|
|
0
|
|
277
|
|
|
|
|
|
|
=item C<< negotiate_serializer ( request_headers => $request_headers, restrict => \@serializer_names, extend => \%media_types ) >> |
278
|
|
|
|
|
|
|
|
279
|
272
|
|
|
|
|
7039
|
Returns a two-element list containing an appropriate media type and |
280
|
|
|
|
|
|
|
L<Attean::API::Serializer> class as decided by L<HTTP::Negotiate>. If the |
281
|
|
|
|
|
|
|
C<< 'request_headers' >> key-value is supplied, the C<< $request_headers >> is |
282
|
|
|
|
|
|
|
passed to C<< HTTP::Negotiate::choose >>. The option C<< 'restrict' >>, set to |
283
|
|
|
|
|
|
|
a list of serializer names, can be used to limit the serializers to choose from. |
284
|
|
|
|
|
|
|
Finally, an C<<'extend'>> option can be set to a hashref that contains |
285
|
|
|
|
|
|
|
MIME-types as keys and a custom variant as value. This will enable the user to |
286
|
|
|
|
|
|
|
use this negotiator to return a type that isn't supported by any serializers. |
287
|
|
|
|
|
|
|
The subsequent code will have to find out how to return a representation. |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=cut |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
my $class = shift; |
292
|
|
|
|
|
|
|
my %options = @_; |
293
|
|
|
|
|
|
|
my $headers = delete $options{ 'request_headers' }; |
294
|
|
|
|
|
|
|
my $restrict = delete $options{ 'restrict' }; |
295
|
|
|
|
|
|
|
my $extend = delete $options{ 'extend' } || {}; |
296
|
|
|
|
|
|
|
my %serializer_names; |
297
|
|
|
|
|
|
|
my %media_types; |
298
|
|
|
|
|
|
|
foreach my $sclass ($class->list_serializers) { |
299
|
9
|
|
|
9
|
1
|
9769
|
my $name = $sclass =~ s/^.*://r; |
300
|
9
|
|
|
|
|
43
|
$serializer_names{lc($name)} = $sclass; |
301
|
9
|
|
|
|
|
25
|
for (@{ $sclass->media_types }) { |
302
|
9
|
|
|
|
|
67
|
push(@{ $media_types{$_} }, $sclass); |
303
|
9
|
|
100
|
|
|
60
|
} |
304
|
9
|
|
|
|
|
23
|
} |
305
|
|
|
|
|
|
|
my %sclasses; |
306
|
9
|
|
|
|
|
44
|
if (ref($restrict) && ref($restrict) eq 'ARRAY') { |
307
|
117
|
|
|
|
|
372
|
foreach (@$restrict) { |
308
|
117
|
|
|
|
|
320
|
if (my $sclass = $serializer_names{lc($_)}) { |
309
|
117
|
|
|
|
|
128
|
$sclasses{ $sclass } = 1; |
|
117
|
|
|
|
|
609
|
|
310
|
135
|
|
|
|
|
135
|
} |
|
135
|
|
|
|
|
329
|
|
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} else { |
313
|
9
|
|
|
|
|
26
|
%sclasses = reverse %serializer_names; |
314
|
9
|
100
|
66
|
|
|
49
|
} |
315
|
3
|
|
|
|
|
6
|
my @default_variants; |
316
|
4
|
100
|
|
|
|
25
|
while (my($type, $sclasses) = each(%media_types)) { |
317
|
3
|
|
|
|
|
8
|
foreach my $sclass (@$sclasses) { |
318
|
|
|
|
|
|
|
next unless $sclasses{$sclass}; |
319
|
|
|
|
|
|
|
my $qv; |
320
|
|
|
|
|
|
|
# slightly prefer turtle as a readable format to others |
321
|
6
|
|
|
|
|
38
|
# try hard to avoid using ntriples as 'text/plain' isn't very useful for conneg |
322
|
|
|
|
|
|
|
if ($type eq 'application/n-triples') { |
323
|
9
|
|
|
|
|
15
|
$qv = 1.0; |
324
|
9
|
|
|
|
|
46
|
} elsif ($type eq 'text/plain') { |
325
|
99
|
|
|
|
|
124
|
$qv = 0.2; |
326
|
135
|
100
|
|
|
|
218
|
} else { |
327
|
95
|
|
|
|
|
85
|
$qv = 0.99; |
328
|
|
|
|
|
|
|
$qv -= 0.01 if ($type =~ m#/x-#); # prefer non experimental media types |
329
|
|
|
|
|
|
|
$qv -= 0.01 if ($type =~ m#^application/(?!rdf[+]xml)#); # prefer standard rdf/xml to other application/* formats |
330
|
95
|
100
|
|
|
|
155
|
} |
|
|
100
|
|
|
|
|
|
331
|
14
|
|
|
|
|
25
|
push(@default_variants, [$type, $qv, $type]); |
332
|
|
|
|
|
|
|
} |
333
|
20
|
|
|
|
|
23
|
} |
334
|
|
|
|
|
|
|
|
335
|
61
|
|
|
|
|
62
|
my %custom_thunks; |
336
|
61
|
50
|
|
|
|
99
|
my @custom_variants; |
337
|
61
|
100
|
|
|
|
119
|
while (my($type,$thunk) = each(%$extend)) { |
338
|
|
|
|
|
|
|
push(@custom_variants, [$thunk, 1.0, $type]); |
339
|
95
|
|
|
|
|
250
|
$custom_thunks{ $thunk } = [$type, $thunk]; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# remove variants with media types that are in custom_variants from @variants |
343
|
9
|
|
|
|
|
25
|
my @variants = grep { not exists $extend->{ $_->[2] } } @default_variants; |
344
|
|
|
|
|
|
|
push(@variants, @custom_variants); |
345
|
9
|
|
|
|
|
44
|
|
346
|
3
|
|
|
|
|
9
|
my $stype = choose( \@variants, $headers ); |
347
|
3
|
|
|
|
|
17
|
if (defined($stype) and $custom_thunks{ $stype }) { |
348
|
|
|
|
|
|
|
my $thunk = $stype; |
349
|
|
|
|
|
|
|
my $type = $custom_thunks{ $stype }[0]; |
350
|
|
|
|
|
|
|
return ($type, $thunk); |
351
|
9
|
|
|
|
|
31
|
} |
|
95
|
|
|
|
|
146
|
|
352
|
9
|
|
|
|
|
19
|
|
353
|
|
|
|
|
|
|
if (defined($stype) and my $sclasses = $media_types{ $stype }) { |
354
|
9
|
|
|
|
|
58
|
return ($stype, $sclasses->[0]); |
355
|
9
|
100
|
100
|
|
|
6059
|
} else { |
356
|
2
|
|
|
|
|
6
|
die "No appropriate serializer found for content-negotiation: " . Data::Dumper->Dump([$headers, $restrict, $extend], [qw(headers restrict extend)]); |
357
|
2
|
|
|
|
|
5
|
} |
358
|
2
|
|
|
|
|
29
|
} |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=item C<< acceptable_parsers ( handles => $item_role, prefer => $parser_role ) >> |
361
|
7
|
100
|
66
|
|
|
31
|
|
362
|
5
|
|
|
|
|
73
|
Returns a string value expressing the media types that are acceptable to the |
363
|
|
|
|
|
|
|
parsers available to the system. This string may be used as an 'Accept' HTTP |
364
|
2
|
|
|
|
|
28
|
header value. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
If a C<< handles >> role is supplied, only parsers that produce objects that |
367
|
|
|
|
|
|
|
conform to C<< $item_role >> will be included. |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
If a C<< prefer >> role is supplied, only parsers that conform to |
370
|
|
|
|
|
|
|
C<< $parser_role >> will be included. |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
Parsers are given a quality-value (expressing a preferred order or use) based |
373
|
|
|
|
|
|
|
on the roles each parser consumes. Parsers consuming L<Attean::API::PullParser> |
374
|
|
|
|
|
|
|
are preferred, while those consuming L<Attean::API::AtOnceParser> are not |
375
|
|
|
|
|
|
|
preferred. An exact ordering between parsers consuming similar roles is |
376
|
|
|
|
|
|
|
currently undefined. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=cut |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
my $class = shift; |
381
|
|
|
|
|
|
|
my %options = @_; |
382
|
|
|
|
|
|
|
my $handles = delete $options{ 'handles' }; |
383
|
|
|
|
|
|
|
my $prefer = delete $options{ 'prefer' }; |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
if (defined($handles) and $handles !~ /::/) { |
386
|
|
|
|
|
|
|
$handles = ucfirst(lc($handles)); |
387
|
|
|
|
|
|
|
$handles = "Attean::API::$handles"; |
388
|
|
|
|
|
|
|
} |
389
|
6
|
|
|
6
|
1
|
32793
|
if (defined($prefer) and $prefer !~ /::/) { |
390
|
6
|
|
|
|
|
15
|
$prefer = "Attean::API::" . ucfirst($prefer); |
391
|
6
|
|
|
|
|
14
|
$prefer = "${prefer}Parser" unless ($prefer =~ /Parser$/); |
392
|
6
|
|
|
|
|
11
|
} |
393
|
|
|
|
|
|
|
|
394
|
6
|
100
|
100
|
|
|
28
|
my %media_types; |
395
|
1
|
|
|
|
|
4
|
foreach my $pclass ($class->list_parsers) { |
396
|
1
|
|
|
|
|
3
|
if (defined($handles)) { |
397
|
|
|
|
|
|
|
my $type = $pclass->handled_type; |
398
|
6
|
100
|
100
|
|
|
27
|
next unless ($type->can('role')); |
399
|
2
|
|
|
|
|
9
|
my $role = $type->role; |
400
|
2
|
50
|
|
|
|
7
|
next unless Role::Tiny::does_role($handles, $role); |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
6
|
|
|
|
|
11
|
if (defined($prefer)) { |
404
|
6
|
|
|
|
|
17
|
next unless ($pclass->does($prefer)); |
405
|
60
|
100
|
|
|
|
509
|
} |
406
|
20
|
|
|
|
|
59
|
|
407
|
20
|
50
|
|
|
|
41
|
my $q = 0.5; |
408
|
20
|
|
|
|
|
157
|
if ($pclass->does('Attean::API::PullParser')) { |
409
|
20
|
100
|
|
|
|
72
|
$q += 0.25; |
410
|
|
|
|
|
|
|
} elsif ($pclass->does('Attean::API::AtOnceParser')) { |
411
|
|
|
|
|
|
|
$q -= 0.25; |
412
|
48
|
100
|
|
|
|
141
|
} |
413
|
30
|
100
|
|
|
|
61
|
|
414
|
|
|
|
|
|
|
for (@{ $pclass->media_types }) { |
415
|
|
|
|
|
|
|
my $mt = "$_;q=$q"; |
416
|
28
|
|
|
|
|
146
|
$media_types{$mt} = $q; |
417
|
28
|
100
|
|
|
|
66
|
} |
|
|
100
|
|
|
|
|
|
418
|
11
|
|
|
|
|
124
|
} |
419
|
|
|
|
|
|
|
|
420
|
5
|
|
|
|
|
158
|
my @sorted = sort { $media_types{$b} <=> $media_types{$a} } keys %media_types; |
421
|
|
|
|
|
|
|
return join(',', @sorted); |
422
|
|
|
|
|
|
|
} |
423
|
28
|
|
|
|
|
753
|
|
|
28
|
|
|
|
|
106
|
|
424
|
39
|
|
|
|
|
120
|
|
425
|
39
|
|
|
|
|
102
|
our %global_functions; |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
=item C<< register_global_function( %uri_to_func ) >> |
428
|
|
|
|
|
|
|
|
429
|
6
|
|
|
|
|
71
|
=cut |
|
72
|
|
|
|
|
100
|
|
430
|
6
|
|
|
|
|
47
|
my $class = shift; |
431
|
|
|
|
|
|
|
my %args = @_; |
432
|
|
|
|
|
|
|
foreach my $uri (keys %args) { |
433
|
|
|
|
|
|
|
my $func = $args{ $uri }; |
434
|
|
|
|
|
|
|
$global_functions{ $uri } = $func; |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
=item C<< get_global_function( $uri ) >> |
439
|
|
|
|
|
|
|
|
440
|
0
|
|
|
0
|
1
|
0
|
=cut |
441
|
0
|
|
|
|
|
0
|
my $class = shift; |
442
|
0
|
|
|
|
|
0
|
my $uri = shift; |
443
|
0
|
|
|
|
|
0
|
return $global_functions{ $uri }; |
444
|
0
|
|
|
|
|
0
|
} |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
our %global_aggregates; |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=item C<< register_global_aggregate( %uri_to_hash ) >> |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=cut |
451
|
|
|
|
|
|
|
my $class = shift; |
452
|
0
|
|
|
0
|
1
|
0
|
my %args = @_; |
453
|
0
|
|
|
|
|
0
|
foreach my $uri (keys %args) { |
454
|
0
|
|
|
|
|
0
|
my $funcs = $args{ $uri }; |
455
|
|
|
|
|
|
|
$global_aggregates{ $uri } = $funcs; |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=item C<< get_global_aggregate( $uri ) >> |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=cut |
462
|
|
|
|
|
|
|
my $class = shift; |
463
|
0
|
|
|
0
|
1
|
0
|
my $uri = shift; |
464
|
0
|
|
|
|
|
0
|
return $global_aggregates{ $uri }; |
465
|
0
|
|
|
|
|
0
|
} |
466
|
0
|
|
|
|
|
0
|
|
467
|
0
|
|
|
|
|
0
|
|
468
|
|
|
|
|
|
|
} |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
1; |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
=back |
474
|
|
|
|
|
|
|
|
475
|
2
|
|
|
2
|
1
|
4
|
=head1 BUGS |
476
|
2
|
|
|
|
|
4
|
|
477
|
2
|
|
|
|
|
14
|
Please report any bugs or feature requests to through the GitHub web interface |
478
|
|
|
|
|
|
|
at L<https://github.com/kasei/attean/issues>. |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
=head1 SEE ALSO |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=head1 AUTHOR |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
Gregory Todd Williams C<< <gwilliams@cpan.org> >> |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=head1 COPYRIGHT |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
Copyright (c) 2014--2022 Gregory Todd Williams. |
491
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under |
492
|
|
|
|
|
|
|
the same terms as Perl itself. |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=cut |