File Coverage

blib/lib/RDF/Trine/Iterator/Bindings.pm
Criterion Covered Total %
statement 263 336 78.2
branch 39 80 48.7
condition 16 31 51.6
subroutine 42 44 95.4
pod 20 20 100.0
total 380 511 74.3


line stmt bran cond sub pod time code
1             # RDF::Trine::Iterator::Bindings
2             # -----------------------------------------------------------------------------
3              
4             =head1 NAME
5              
6             RDF::Trine::Iterator::Bindings - Iterator class for bindings query results
7              
8             =head1 VERSION
9              
10             This document describes RDF::Trine::Iterator::Bindings version 1.017
11              
12             =head1 SYNOPSIS
13              
14             use RDF::Trine::Iterator::Bindings;
15            
16             my $iterator = RDF::Trine::Iterator::Bindings->new( \&data, \@names );
17             while (my $row = $iterator->next) {
18             # $row is a HASHref containing variable name -> RDF Term bindings
19             my @vars = keys %$row;
20             print $row->{ 'var' }->as_string;
21             }
22              
23             =head1 METHODS
24              
25             Beyond the methods documented below, this class inherits methods from the
26             L<RDF::Trine::Iterator> class.
27              
28             =over 4
29              
30             =cut
31              
32             package RDF::Trine::Iterator::Bindings;
33              
34 68     68   446 use utf8;
  68         182  
  68         532  
35 68     68   1867 use strict;
  68         143  
  68         1108  
36 68     68   305 use warnings;
  68         133  
  68         1639  
37 68     68   351 no warnings 'redefine';
  68         134  
  68         2218  
38 68     68   339 use Data::Dumper;
  68         133  
  68         2799  
39              
40 68     68   355 use JSON 2.0;
  68         1227  
  68         390  
41 68     68   36762 use Text::Table;
  68         404548  
  68         1872  
42 68     68   643 use Log::Log4perl;
  68         159  
  68         732  
43 68     68   4451 use Scalar::Util qw(blessed reftype);
  68         183  
  68         3655  
44 68     68   26665 use RDF::Trine::Iterator::Bindings::Materialized;
  68         192  
  68         1586  
45 68     68   25849 use RDF::Trine::Serializer::Turtle;
  68         218  
  68         2690  
46              
47 68     68   515 use RDF::Trine::Iterator qw(smap);
  68         146  
  68         4207  
48 68     68   415 use base qw(RDF::Trine::Iterator);
  68         147  
  68         5741  
49              
50 68     68   449 use Carp qw(croak);
  68         152  
  68         4333  
51              
52             our ($VERSION);
53             BEGIN {
54 68     68   48957 $VERSION = '1.017';
55             }
56              
57             =item C<new ( \@results, \@names, %args )>
58              
59             =item C<new ( \&results, \@names, %args )>
60              
61             Returns a new SPARQL Result interator object. Results must be either
62             a reference to an array containing results or a CODE reference that
63             acts as an iterator, returning successive items when called, and
64             returning undef when the iterator is exhausted.
65              
66             =cut
67              
68             sub new {
69 1924     1924 1 16770 my $class = shift;
70 1924   100 1   5151 my $stream = shift || sub { undef };
  1         3  
71 1924   100     6710 my $names = shift || [];
72 1924         5136 my %args = @_;
73            
74 1924         3660 my $type = 'bindings';
75 1924         7090 my $self = $class->SUPER::new( $stream, $type, $names, %args );
76            
77 1924   100     7234 my $s = $args{ sorted_by } || [];
78 1924         6394 $self->{sorted_by} = $s;
79 1924         8597 return $self;
80             }
81              
82             sub _new {
83 10     10   25 my $class = shift;
84 10         19 my $stream = shift;
85 10         24 my $type = shift;
86 10         21 my $names = shift;
87 10         28 my %args = @_;
88 10         60 return $class->new( $stream, $names, %args );
89             }
90              
91             =item C<< materialize >>
92              
93             Returns a materialized version of the current binding iterator.
94             The materialization process will leave this iterator empty. The materialized
95             iterator that is returned should be used for any future need for the iterator's
96             data.
97              
98             =cut
99              
100             sub materialize {
101 3     3 1 545 my $self = shift;
102 3         19 my @data = $self->get_all;
103 3         16 my @args = $self->construct_args;
104 3         13 return $self->_mclass->_new( \@data, @args );
105             }
106              
107             sub _mclass {
108 3     3   39 return 'RDF::Trine::Iterator::Bindings::Materialized';
109             }
110              
111             =item C<< project ( @columns ) >>
112              
113             Returns a new stream that projects the current bindings to only the given columns.
114              
115             =cut
116              
117             sub project {
118 8     8 1 22 my $self = shift;
119 8         19 my $class = ref($self);
120 8         22 my @proj = @_;
121            
122             my $sub = sub {
123 30     30   78 my $row = $self->next;
124 30 100       80 return unless ($row);
125 22         44 my $p = { map { $_ => $row->{ $_ } } @proj };
  42         109  
126 22         55 return $p;
127 8         27 };
128            
129 8         33 my $args = $self->_args();
130 8         34 my $proj = $class->new( $sub, [@proj], %$args );
131 8         20 return $proj;
132             }
133              
134             =item C<join_streams ( $stream, $stream )>
135              
136             Performs a natural, nested loop join of the two streams, returning a new stream
137             of joined results.
138              
139             =cut
140              
141             sub join_streams {
142 3     3 1 17 my $self = shift;
143 3         5 my $a = shift;
144 3         6 my $b = shift;
145             # my $bridge = shift;
146 3         7 my %args = @_;
147            
148             # Carp::confess unless ($a->isa('RDF::Trine::Iterator::Bindings'));
149             # Carp::confess unless ($b->isa('RDF::Trine::Iterator::Bindings'));
150 3         20 my $l = Log::Log4perl->get_logger("rdf.trine.iterator.bindings");
151            
152 3         545 my @join_sorted_by;
153 3 50       11 if (my $o = $args{ orderby }) {
154 0         0 my $req_sort = join(',', map { $_->[1]->name => $_->[0] } @$o);
  0         0  
155 0         0 my $a_sort = join(',', $a->sorted_by);
156 0         0 my $b_sort = join(',', $b->sorted_by);
157            
158 0 0       0 if ($l->is_debug) {
159 0         0 $l->debug('---------------------------');
160 0         0 $l->debug('REQUESTED SORT in JOIN: ' . Dumper($req_sort));
161 0         0 $l->debug('JOIN STREAM SORTED BY: ' . Dumper($a_sort));
162 0         0 $l->debug('JOIN STREAM SORTED BY: ' . Dumper($b_sort));
163             }
164 0         0 my $actual_sort;
165 0 0       0 if (substr( $a_sort, 0, length($req_sort) ) eq $req_sort) {
    0          
166 0         0 $l->debug("first stream is already sorted. using it in the outer loop.");
167             } elsif (substr( $b_sort, 0, length($req_sort) ) eq $req_sort) {
168 0         0 $l->debug("second stream is already sorted. using it in the outer loop.");
169 0         0 ($a,$b) = ($b,$a);
170             } else {
171 0         0 my $a_common = join('!', $a_sort, $req_sort);
172 0         0 my $b_common = join('!', $b_sort, $req_sort);
173 0 0       0 if ($a_common =~ qr[^([^!]+)[^!]*!\1]) { # shared prefix between $a_sort and $req_sort?
    0          
174 0         0 $l->debug("first stream is closely sorted ($1).");
175             } elsif ($b_common =~ qr[^([^!]+)[^!]*!\1]) { # shared prefix between $b_sort and $req_sort?
176 0         0 $l->debug("second stream is closely sorted ($1).");
177 0         0 ($a,$b) = ($b,$a);
178             }
179             }
180 0         0 @join_sorted_by = ($a->sorted_by, $b->sorted_by);
181             }
182            
183 3         11 my $stream = $self->nested_loop_join( $a, $b, %args );
184 3         20 $l->debug("JOINED stream is sorted by: " . join(',', @join_sorted_by));
185 3         30 $stream->{sorted_by} = \@join_sorted_by;
186 3         10 return $stream;
187             }
188              
189             =item C<< nested_loop_join ( $outer, $inner ) >>
190              
191             Performs a natural, nested loop join of the two streams, returning a new stream
192             of joined results.
193              
194             Note that the values from the C<< $inner >> iterator are fully materialized for
195             this join, and the results of the join are in the order of values from the
196             C<< $outer >> iterator. This suggests that:
197              
198             * If sorting needs to be preserved, the C<< $outer >> iterator should be used to
199             determine the result ordering.
200              
201             * If one iterator is much smaller than the other, it should likely be used as
202             the C<< $inner >> iterator since materialization will require less total memory.
203              
204             =cut
205              
206             sub nested_loop_join {
207 3     3 1 6 my $self = shift;
208 3         6 my $astream = shift;
209 3         5 my $bstream = shift;
210             # my $bridge = shift;
211 3         7 my %args = @_;
212            
213             # Carp::confess unless ($astream->isa('RDF::Trine::Iterator::Bindings'));
214             # Carp::confess unless ($bstream->isa('RDF::Trine::Iterator::Bindings'));
215 3         9 my $l = Log::Log4perl->get_logger("rdf.trine.iterator.bindings");
216            
217 3         67 my @names = RDF::Trine::_uniq( map { $_->binding_names() } ($astream, $bstream) );
  6         14  
218 3         11 my $a = $astream->project( @names );
219 3         8 my $b = $bstream->project( @names );
220            
221            
222 3         14 my @data = $b->get_all();
223 68     68   541 no warnings 'uninitialized';
  68         183  
  68         119666  
224            
225 3         8 my $inner_index;
226             my $rowa;
227 3         10 my $need_new_a = 1;
228             my $sub = sub {
229 15     15   29 OUTER: while (1) {
230 24 100       80 if ($need_new_a) {
231 12         37 $l->debug("### fetching new outer tuple");
232 12         99 $rowa = $a->next;
233 12         24 $inner_index = 0;
234 12         23 $need_new_a = 0;
235             }
236 24         71 $l->debug("OUTER: " . Dumper($rowa));
237 24 100       1536 return unless ($rowa);
238 21         58 LOOP: while ($inner_index <= $#data) {
239 28         58 my $rowb = $data[ $inner_index++ ];
240 28         92 $l->debug("- INNER[ $inner_index ]: " . Dumper($rowb));
241 28 100       1573 $l->debug("[--JOIN--] " . join(' ', map { my $row = $_; '{' . join(', ', map { join('=', $_, ($row->{$_}) ? $row->{$_}->as_string : '(undef)') } (keys %$row)) . '}' } ($rowa, $rowb)));
  56         96  
  56         121  
  112         485  
242 28         252 my %keysa = map {$_=>1} (keys %$rowa);
  56         132  
243 28         61 my @shared = grep { $keysa{ $_ } } (keys %$rowb);
  56         119  
244 28         57 foreach my $key (@shared) {
245 42         64 my $val_a = $rowa->{ $key };
246 42         63 my $val_b = $rowb->{ $key };
247 42         69 my $defined = 0;
248 42         72 foreach my $n ($val_a, $val_b) {
249 84 100       191 $defined++ if (defined($n));
250             }
251 42 100       95 if ($defined == 2) {
252 24         75 my $equal = $val_a->equal( $val_b );
253 24 100       64 unless ($equal) {
254 16         43 $l->debug("can't join because mismatch of $key (" . join(' <==> ', map {$_->as_string} ($val_a, $val_b)) . ")");
  32         77  
255 16         173 next LOOP;
256             }
257             }
258             }
259            
260 12         27 my $row = { (map { $_ => $rowa->{$_} } grep { defined($rowa->{$_}) } keys %$rowa), (map { $_ => $rowb->{$_} } grep { defined($rowb->{$_}) } keys %$rowb) };
  16         38  
  24         53  
  16         43  
  24         45  
261 12 50       34 if ($l->is_debug) {
262 0         0 $l->debug("JOINED:");
263 0         0 foreach my $key (keys %$row) {
264 0         0 $l->debug("$key\t=> " . $row->{ $key }->as_string);
265             }
266             }
267 12         90 return $row;
268             }
269 9         18 $need_new_a = 1;
270             }
271 3         18 };
272            
273 3         11 my $args = $astream->_args;
274 3         11 return $astream->_new( $sub, 'bindings', \@names, %$args );
275             }
276              
277              
278             =item C<< sorted_by >>
279              
280             =cut
281              
282             sub sorted_by {
283 21     21 1 132 my $self = shift;
284 21         56 my $sorted = $self->{sorted_by};
285 21         162 return @$sorted;
286             }
287              
288             =item C<< binding_value_by_name ( $name ) >>
289              
290             Returns the binding of the named variable in the current result.
291              
292             =cut
293              
294             sub binding_value_by_name {
295 4     4 1 12 my $self = shift;
296 4         9 my $name = shift;
297 4 50       27 my $row = ($self->open) ? $self->current : $self->next;
298 4 50       18 if (exists( $row->{ $name } )) {
299 4         21 return $row->{ $name };
300             } else {
301             # warn "No variable named '$name' is present in query results.\n";
302 0         0 return;
303             }
304             }
305              
306             =item C<< binding_value ( $i ) >>
307              
308             Returns the binding of the $i-th variable in the current result.
309              
310             =cut
311              
312             sub binding_value {
313 2     2 1 6 my $self = shift;
314 2         5 my $val = shift;
315 2         7 my @names = $self->binding_names;
316 2         9 return $self->binding_value_by_name( $names[ $val ] );
317             }
318              
319              
320             =item C<binding_values>
321              
322             Returns a list of the binding values from the current result.
323              
324             =cut
325              
326             sub binding_values {
327 2     2 1 5 my $self = shift;
328 2 50       11 my $row = ($self->open) ? $self->current : $self->next;
329 2         8 return @{ $row }{ $self->binding_names };
  2         9  
330             }
331              
332              
333             =item C<binding_names>
334              
335             Returns a list of the binding names.
336              
337             =cut
338              
339             sub binding_names {
340 22     22 1 1656 my $self = shift;
341 22         46 my $names = $self->{_names};
342 22         78 return @$names;
343             }
344              
345             =item C<binding_name ( $i )>
346              
347             Returns the name of the $i-th result column.
348              
349             =cut
350              
351             sub binding_name {
352 14     14 1 1236 my $self = shift;
353 14         22 my $val = shift;
354 14         35 my $names = $self->{_names};
355 14         40 return $names->[ $val ];
356             }
357              
358              
359             =item C<bindings_count>
360              
361             Returns the number of variable bindings in the current result.
362              
363             =cut
364              
365             sub bindings_count {
366 5     5 1 1173 my $self = shift;
367 5         85 my $names = $self->{_names};
368 5 100       35 return scalar( @$names ) if (scalar(@$names));
369 1         3 return 0;
370             }
371              
372             =item C<is_bindings>
373              
374             Returns true if the underlying result is a set of variable bindings.
375              
376             =cut
377              
378             sub is_bindings {
379 3     3 1 1382 my $self = shift;
380 3         20 return 1;
381             }
382              
383             =item C<as_json ( $max_size )>
384              
385             Returns a JSON serialization of the stream data.
386              
387             =cut
388              
389             sub as_json {
390 2     2 1 14 my $self = shift;
391 2   50     9 my $max_result_size = shift || 0;
392 2         6 my $width = $self->bindings_count;
393             # my $bridge = $self->bridge;
394            
395 2         5 my @variables;
396 2         6 for (my $i=0; $i < $width; $i++) {
397 3         10 my $name = $self->binding_name($i);
398 3 50       10 push(@variables, $name) if $name;
399             }
400            
401 2         4 my $count = 0;
402 2         6 my @sorted = $self->sorted_by;
403 2 50       13 my $order = scalar(@sorted) ? JSON::true : JSON::false;
404 2 50       10 my $dist = $self->_args->{distinct} ? JSON::true : JSON::false;
405            
406 2         15 my $data = {
407             head => { vars => \@variables },
408             results => { ordered => $order, distinct => $dist, bindings => [] },
409             };
410 2         4 my @bindings;
411 2         8 while (my $row = $self->next) {
412 0         0 my %row = map { format_node_json($row->{$_}, $_) } (keys %$row);
  0         0  
413 0         0 push(@{ $data->{results}{bindings} }, \%row);
  0         0  
414 0 0 0     0 last if ($max_result_size and ++$count >= $max_result_size);
415             }
416            
417 2         9 return to_json( $data );
418             }
419              
420             =item C<as_xml ( $max_size )>
421              
422             Returns an XML serialization of the stream data.
423              
424             =cut
425              
426             sub as_xml {
427 1     1 1 3 my $self = shift;
428 1   50     6 my $max_result_size = shift || 0;
429 1         3 my $string = '';
430 1     1   60 open( my $fh, '>', \$string );
  1         9  
  1         2  
  1         7  
431 1         961 $self->print_xml( $fh, $max_result_size );
432 1         3 close($fh);
433 1         12 return $string;
434             }
435              
436             =item C<as_string ( $max_size [, \$count] )>
437              
438             Returns a string table serialization of the stream data.
439              
440             =cut
441              
442             sub as_string {
443 0     0 1 0 my $self = shift;
444 0   0     0 my $max_result_size = shift || 0;
445 0         0 my $rescount = shift;
446 0         0 my @names = $self->binding_names;
447 0         0 my $headers = \@names;
448 0         0 my @rows;
449 0         0 my $count = 0;
450 0         0 while (my $row = $self->next) {
451 0 0       0 push(@rows, [ map { blessed($_) ? RDF::Trine::Serializer::Turtle->node_as_concise_string($_) : '' } @{ $row }{ @names } ]);
  0         0  
  0         0  
452 0 0 0     0 last if ($max_result_size and ++$count >= $max_result_size);
453             }
454 0 0       0 if (ref($rescount)) {
455 0         0 $$rescount = scalar(@rows);
456             }
457            
458 0         0 my @rule = qw(- +);
459 0         0 my @headers = (\q"| ");
460 0         0 push(@headers, map { $_ => \q" | " } @$headers);
  0         0  
461 0         0 pop @headers;
462 0         0 push @headers => (\q" |");
463            
464 0 0       0 if ('ARRAY' eq ref $rows[0]) {
465 0 0       0 if (@$headers == @{ $rows[0] }) {
  0         0  
466 0         0 my $table = Text::Table->new(@headers);
467 0         0 $table->rule(@rule);
468 0         0 $table->body_rule(@rule);
469 0         0 $table->load(@rows);
470            
471             return join('',
472             $table->rule(@rule),
473             $table->title,
474             $table->rule(@rule),
475 0         0 map({ $table->body($_) } 0 .. @rows),
  0         0  
476             $table->rule(@rule)
477             );
478             } else {
479 0         0 die("make_table() rows must be an AoA with rows being same size as headers");
480             }
481             } else {
482 0         0 return '';
483             }
484             }
485              
486             =item C<< as_statements ( $pattern | @names ) >>
487              
488             Returns a L<RDF::Trine::Iterator::Graph> with the statements of the stream.
489              
490             If C<$pattern>, an RDF::Trine::Pattern object, is given as an argument, each of
491             its triples are instantiated with variable bindings from each row of the
492             iterator, and returned as RDF::Trine::Statement objects from a new
493             RDF::Trine::Iterator::Graph iterator.
494              
495             If 3 variable C<@names> are supplied, their corresponding variable bindings
496             in each row of the iterator are used (in order) as the subject, predicate, and
497             object of new RDF::Trine::Statement objects and returned from a new
498             RDF::Trine::Iterator::Graph iterator.
499              
500             =cut
501              
502             sub as_statements {
503 866     866 1 1890 my $self = shift;
504 866         2899 my @names = @_;
505 866 100 66     3704 if (scalar(@names) == 1 and $names[0]->can('triples')) {
506 1         4 my $pattern = shift;
507 1         5 my @triples = $pattern->triples;
508 1         2 my @queue;
509             my $sub = sub {
510 4     4   6 while (1) {
511 6 100       15 if (scalar(@queue)) {
512 3         9 return shift(@queue);
513             }
514 3         11 my $row = $self->next;
515 3 100       9 return unless (defined $row);
516 2         6 foreach my $t (@triples) {
517 4         12 my $st = $t->bind_variables($row);
518 4 100       12 if ($st->rdf_compatible) {
519 3         7 push(@queue, $st);
520             }
521             }
522             }
523 1         5 };
524 1         10 return RDF::Trine::Iterator::Graph->new( $sub );
525             } else {
526             my $sub = sub {
527 2514     2514   6777 my $row = $self->next;
528 2514 100       6688 return unless (defined $row);
529 1649         3061 my @values = @{ $row }{ @names };
  1649         4893  
530 1649 100 100     11207 my $statement = (scalar(@values) == 3 or not(defined($values[3])))
531             ? RDF::Trine::Statement->new( @values[ 0 .. 2 ] )
532             : RDF::Trine::Statement::Quad->new( @values );
533 1649         4243 return $statement;
534 865         3825 };
535 865         3458 return RDF::Trine::Iterator::Graph->new( $sub );
536             }
537             }
538              
539             =item C<< print_xml ( $fh, $max_size ) >>
540              
541             Prints an XML serialization of the stream data to the filehandle $fh.
542              
543             =cut
544              
545             sub print_xml {
546 1     1 1 4 my $self = shift;
547 1         3 my $fh = shift;
548 1   50     11 my $max_result_size = shift || 0;
549 1         8 my $width = $self->bindings_count;
550            
551 1         5 my @variables;
552 1         7 for (my $i=0; $i < $width; $i++) {
553 3         12 my $name = $self->binding_name($i);
554 3 50       16 push(@variables, $name) if $name;
555             }
556            
557 1         4 print {$fh} <<"END";
  1         8  
558             <?xml version="1.0" encoding="utf-8"?>
559             <sparql xmlns="http://www.w3.org/2005/sparql-results#">
560             <head>
561             END
562            
563 1         3 my $t = join("\n", map { qq(\t<variable name="$_"/>) } @variables);
  3         12  
564            
565 1 50       4 if ($t) {
566 1         2 print {$fh} "${t}\n";
  1         4  
567             }
568            
569 1         3 print {$fh} <<"END";
  1         4  
570             </head>
571             <results>
572             END
573            
574 1         2 my $count = 0;
575 1         5 while (my $row = $self->next) {
576 2         5 my @row;
577 2         4 print {$fh} "\t\t<result>\n";
  2         5  
578 2         7 for (my $i = 0; $i < $width; $i++) {
579 6         15 my $name = $self->binding_name($i);
580 6         12 my $value = $row->{ $name };
581 6         8 print {$fh} "\t\t\t" . $self->format_node_xml($value, $name) . "\n";
  6         24  
582             }
583 2         5 print {$fh} "\t\t</result>\n";
  2         5  
584            
585 2 50 33     11 last if ($max_result_size and ++$count >= $max_result_size);
586             }
587            
588 1         3 print {$fh} "</results>\n";
  1         4  
589 1         3 print {$fh} "</sparql>\n";
  1         5  
590             }
591              
592             =begin private
593              
594             =item C<format_node_json ( $node, $name )>
595              
596             Returns a string representation of C<$node> for use in a JSON serialization.
597              
598             =end private
599              
600             =cut
601              
602             sub format_node_json {
603             # my $bridge = shift;
604             # return unless ($bridge);
605            
606 0     0 1 0 my $node = shift;
607 0         0 my $name = shift;
608 0         0 my $node_label;
609            
610 0 0       0 if(!defined $node) {
    0          
    0          
    0          
611 0         0 return;
612             } elsif ($node->isa('RDF::Trine::Node::Resource')) {
613 0         0 $node_label = $node->uri_value;
614 0         0 return $name => { type => 'uri', value => $node_label };
615             } elsif ($node->isa('RDF::Trine::Node::Literal')) {
616 0         0 $node_label = $node->literal_value;
617 0         0 return $name => { type => 'literal', value => $node_label };
618             } elsif ($node->isa('RDF::Trine::Node::Blank')) {
619 0         0 $node_label = $node->blank_identifier;
620 0         0 return $name => { type => 'bnode', value => $node_label };
621             } else {
622 0         0 return;
623             }
624             }
625              
626             =item C<< construct_args >>
627              
628             Returns the arguments necessary to pass to the stream constructor _new
629             to re-create this stream (assuming the same closure as the first
630             argument).
631              
632             =cut
633              
634             sub construct_args {
635 8     8 1 25 my $self = shift;
636 8         53 my $type = $self->type;
637 8         31 my @names = $self->binding_names;
638 8   50     38 my $args = $self->_args || {};
639 8         20 return ($type, \@names, %{ $args });
  8         48  
640             }
641              
642              
643             1;
644              
645             __END__
646              
647             =back
648              
649             =head1 DEPENDENCIES
650              
651             L<JSON|JSON>
652              
653             L<Scalar::Util|Scalar::Util>
654              
655             =head1 BUGS
656              
657             Please report any bugs or feature requests to through the GitHub web interface
658             at L<https://github.com/kasei/perlrdf/issues>.
659              
660             =head1 AUTHOR
661              
662             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
663              
664             =head1 COPYRIGHT
665              
666             Copyright (c) 2006-2012 Gregory Todd Williams. This
667             program is free software; you can redistribute it and/or modify it under
668             the same terms as Perl itself.
669              
670             =cut