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.018
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   429 use utf8;
  68         155  
  68         517  
35 68     68   1879 use strict;
  68         142  
  68         1102  
36 68     68   302 use warnings;
  68         146  
  68         1683  
37 68     68   308 no warnings 'redefine';
  68         137  
  68         2116  
38 68     68   324 use Data::Dumper;
  68         148  
  68         2726  
39              
40 68     68   346 use JSON 2.0;
  68         1265  
  68         414  
41 68     68   37794 use Text::Table;
  68         402136  
  68         1798  
42 68     68   629 use Log::Log4perl;
  68         149  
  68         660  
43 68     68   4100 use Scalar::Util qw(blessed reftype);
  68         151  
  68         3114  
44 68     68   26479 use RDF::Trine::Iterator::Bindings::Materialized;
  68         181  
  68         1552  
45 68     68   25668 use RDF::Trine::Serializer::Turtle;
  68         219  
  68         2610  
46              
47 68     68   490 use RDF::Trine::Iterator qw(smap);
  68         150  
  68         3979  
48 68     68   399 use base qw(RDF::Trine::Iterator);
  68         143  
  68         5406  
49              
50 68     68   433 use Carp qw(croak);
  68         143  
  68         4101  
51              
52             our ($VERSION);
53             BEGIN {
54 68     68   46812 $VERSION = '1.018';
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 16586 my $class = shift;
70 1924   100 1   4914 my $stream = shift || sub { undef };
  1         3  
71 1924   100     6273 my $names = shift || [];
72 1924         4710 my %args = @_;
73            
74 1924         3491 my $type = 'bindings';
75 1924         7101 my $self = $class->SUPER::new( $stream, $type, $names, %args );
76            
77 1924   100     6748 my $s = $args{ sorted_by } || [];
78 1924         5826 $self->{sorted_by} = $s;
79 1924         8228 return $self;
80             }
81              
82             sub _new {
83 10     10   20 my $class = shift;
84 10         17 my $stream = shift;
85 10         19 my $type = shift;
86 10         19 my $names = shift;
87 10         22 my %args = @_;
88 10         39 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 783 my $self = shift;
102 3         18 my @data = $self->get_all;
103 3         13 my @args = $self->construct_args;
104 3         11 return $self->_mclass->_new( \@data, @args );
105             }
106              
107             sub _mclass {
108 3     3   29 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 19 my $self = shift;
119 8         17 my $class = ref($self);
120 8         18 my @proj = @_;
121            
122             my $sub = sub {
123 30     30   75 my $row = $self->next;
124 30 100       78 return unless ($row);
125 22         47 my $p = { map { $_ => $row->{ $_ } } @proj };
  42         116  
126 22         57 return $p;
127 8         28 };
128            
129 8         33 my $args = $self->_args();
130 8         30 my $proj = $class->new( $sub, [@proj], %$args );
131 8         18 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 14 my $self = shift;
143 3         6 my $a = shift;
144 3         4 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         21 my $l = Log::Log4perl->get_logger("rdf.trine.iterator.bindings");
151            
152 3         580 my @join_sorted_by;
153 3 50       12 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         10 my $stream = $self->nested_loop_join( $a, $b, %args );
184 3         18 $l->debug("JOINED stream is sorted by: " . join(',', @join_sorted_by));
185 3         29 $stream->{sorted_by} = \@join_sorted_by;
186 3         7 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 7 my $self = shift;
208 3         6 my $astream = shift;
209 3         5 my $bstream = shift;
210             # my $bridge = shift;
211 3         6 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         11 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         15  
218 3         11 my $a = $astream->project( @names );
219 3         7 my $b = $bstream->project( @names );
220            
221            
222 3         19 my @data = $b->get_all();
223 68     68   504 no warnings 'uninitialized';
  68         163  
  68         118577  
224            
225 3         9 my $inner_index;
226             my $rowa;
227 3         5 my $need_new_a = 1;
228             my $sub = sub {
229 15     15   25 OUTER: while (1) {
230 24 100       58 if ($need_new_a) {
231 12         36 $l->debug("### fetching new outer tuple");
232 12         100 $rowa = $a->next;
233 12         27 $inner_index = 0;
234 12         20 $need_new_a = 0;
235             }
236 24         76 $l->debug("OUTER: " . Dumper($rowa));
237 24 100       1586 return unless ($rowa);
238 21         55 LOOP: while ($inner_index <= $#data) {
239 28         64 my $rowb = $data[ $inner_index++ ];
240 28         92 $l->debug("- INNER[ $inner_index ]: " . Dumper($rowb));
241 28 100       1689 $l->debug("[--JOIN--] " . join(' ', map { my $row = $_; '{' . join(', ', map { join('=', $_, ($row->{$_}) ? $row->{$_}->as_string : '(undef)') } (keys %$row)) . '}' } ($rowa, $rowb)));
  56         94  
  56         134  
  112         541  
242 28         244 my %keysa = map {$_=>1} (keys %$rowa);
  56         143  
243 28         66 my @shared = grep { $keysa{ $_ } } (keys %$rowb);
  56         122  
244 28         62 foreach my $key (@shared) {
245 44         65 my $val_a = $rowa->{ $key };
246 44         71 my $val_b = $rowb->{ $key };
247 44         74 my $defined = 0;
248 44         78 foreach my $n ($val_a, $val_b) {
249 88 100       197 $defined++ if (defined($n));
250             }
251 44 100       109 if ($defined == 2) {
252 24         83 my $equal = $val_a->equal( $val_b );
253 24 100       66 unless ($equal) {
254 16         43 $l->debug("can't join because mismatch of $key (" . join(' <==> ', map {$_->as_string} ($val_a, $val_b)) . ")");
  32         83  
255 16         185 next LOOP;
256             }
257             }
258             }
259            
260 12         28 my $row = { (map { $_ => $rowa->{$_} } grep { defined($rowa->{$_}) } keys %$rowa), (map { $_ => $rowb->{$_} } grep { defined($rowb->{$_}) } keys %$rowb) };
  16         41  
  24         61  
  16         44  
  24         54  
261 12 50       40 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         94 return $row;
268             }
269 9         17 $need_new_a = 1;
270             }
271 3         24 };
272            
273 3         12 my $args = $astream->_args;
274 3         13 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 136 my $self = shift;
284 21         50 my $sorted = $self->{sorted_by};
285 21         157 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 7 my $self = shift;
296 4         8 my $name = shift;
297 4 50       15 my $row = ($self->open) ? $self->current : $self->next;
298 4 50       10 if (exists( $row->{ $name } )) {
299 4         16 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 5 my $self = shift;
314 2         3 my $val = shift;
315 2         6 my @names = $self->binding_names;
316 2         7 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       7 my $row = ($self->open) ? $self->current : $self->next;
329 2         5 return @{ $row }{ $self->binding_names };
  2         8  
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 1916 my $self = shift;
341 22         45 my $names = $self->{_names};
342 22         69 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 1329 my $self = shift;
353 14         20 my $val = shift;
354 14         26 my $names = $self->{_names};
355 14         32 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 1273 my $self = shift;
367 5         45 my $names = $self->{_names};
368 5 100       27 return scalar( @$names ) if (scalar(@$names));
369 1         2 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 1617 my $self = shift;
380 3         12 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 12 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         3 my @variables;
396 2         7 for (my $i=0; $i < $width; $i++) {
397 3         7 my $name = $self->binding_name($i);
398 3 50       11 push(@variables, $name) if $name;
399             }
400            
401 2         3 my $count = 0;
402 2         6 my @sorted = $self->sorted_by;
403 2 50       10 my $order = scalar(@sorted) ? JSON::true : JSON::false;
404 2 50       10 my $dist = $self->_args->{distinct} ? JSON::true : JSON::false;
405            
406 2         13 my $data = {
407             head => { vars => \@variables },
408             results => { ordered => $order, distinct => $dist, bindings => [] },
409             };
410 2         5 my @bindings;
411 2         6 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         7 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 2 my $self = shift;
428 1   50     5 my $max_result_size = shift || 0;
429 1         3 my $string = '';
430 1     1   36 open( my $fh, '>', \$string );
  1         7  
  1         2  
  1         5  
431 1         713 $self->print_xml( $fh, $max_result_size );
432 1         3 close($fh);
433 1         5 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 1735 my $self = shift;
504 866         2480 my @names = @_;
505 866 100 66     3181 if (scalar(@names) == 1 and $names[0]->can('triples')) {
506 1         3 my $pattern = shift;
507 1         4 my @triples = $pattern->triples;
508 1         3 my @queue;
509             my $sub = sub {
510 4     4   7 while (1) {
511 6 100       15 if (scalar(@queue)) {
512 3         9 return shift(@queue);
513             }
514 3         10 my $row = $self->next;
515 3 100       10 return unless (defined $row);
516 2         4 foreach my $t (@triples) {
517 4         12 my $st = $t->bind_variables($row);
518 4 100       14 if ($st->rdf_compatible) {
519 3         9 push(@queue, $st);
520             }
521             }
522             }
523 1         5 };
524 1         7 return RDF::Trine::Iterator::Graph->new( $sub );
525             } else {
526             my $sub = sub {
527 2514     2514   6087 my $row = $self->next;
528 2514 100       6280 return unless (defined $row);
529 1649         2780 my @values = @{ $row }{ @names };
  1649         4725  
530 1649 100 100     10315 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         3988 return $statement;
534 865         3421 };
535 865         3329 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         2 my $fh = shift;
548 1   50     6 my $max_result_size = shift || 0;
549 1         3 my $width = $self->bindings_count;
550            
551 1         2 my @variables;
552 1         4 for (my $i=0; $i < $width; $i++) {
553 3         7 my $name = $self->binding_name($i);
554 3 50       11 push(@variables, $name) if $name;
555             }
556            
557 1         3 print {$fh} <<"END";
  1         4  
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         10  
564            
565 1 50       4 if ($t) {
566 1         2 print {$fh} "${t}\n";
  1         4  
567             }
568            
569 1         2 print {$fh} <<"END";
  1         3  
570             </head>
571             <results>
572             END
573            
574 1         2 my $count = 0;
575 1         5 while (my $row = $self->next) {
576 2         3 my @row;
577 2         5 print {$fh} "\t\t<result>\n";
  2         4  
578 2         7 for (my $i = 0; $i < $width; $i++) {
579 6         16 my $name = $self->binding_name($i);
580 6         9 my $value = $row->{ $name };
581 6         10 print {$fh} "\t\t\t" . $self->format_node_xml($value, $name) . "\n";
  6         42  
582             }
583 2         4 print {$fh} "\t\t</result>\n";
  2         4  
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         2  
589 1         2 print {$fh} "</sparql>\n";
  1         4  
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 21 my $self = shift;
636 8         40 my $type = $self->type;
637 8         26 my @names = $self->binding_names;
638 8   50     35 my $args = $self->_args || {};
639 8         19 return ($type, \@names, %{ $args });
  8         36  
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