File Coverage

blib/lib/RDF/Query/Node/Literal.pm
Criterion Covered Total %
statement 119 171 69.5
branch 41 80 51.2
condition 21 52 40.3
subroutine 25 29 86.2
pod 7 7 100.0
total 213 339 62.8


line stmt bran cond sub pod time code
1             # RDF::Query::Node::Literal
2             # -----------------------------------------------------------------------------
3              
4             =head1 NAME
5              
6             RDF::Query::Node::Literal - RDF Node class for literals
7              
8             =head1 VERSION
9              
10             This document describes RDF::Query::Node::Literal version 2.916.
11              
12             =cut
13              
14             package RDF::Query::Node::Literal;
15              
16 36     36   241 use strict;
  36         74  
  36         988  
17 36     36   254 use warnings;
  36         65  
  36         951  
18 36     36   183 no warnings 'redefine';
  36         71  
  36         1178  
19 36     36   178 use base qw(RDF::Query::Node RDF::Trine::Node::Literal);
  36         67  
  36         3717  
20              
21 36     36   2889 use DateTime;
  36         294926  
  36         948  
22 36     36   1869 use DateTime::Format::W3CDTF;
  36         1680  
  36         948  
23 36     36   1232 use RDF::Query::Error;
  36         133  
  36         318  
24 36     36   1915 use Data::Dumper;
  36         80  
  36         1694  
25 36     36   2902 use Log::Log4perl;
  36         110722  
  36         299  
26 36     36   1855 use Scalar::Util qw(blessed refaddr looks_like_number);
  36         74  
  36         2179  
27 36     36   192 use Carp qw(carp croak confess);
  36         79  
  36         2844  
28              
29             ######################################################################
30              
31             our ($VERSION, $LAZY_COMPARISONS);
32             BEGIN {
33 36     36   7109 $VERSION = '2.916';
34             }
35              
36             ######################################################################
37              
38             use overload '<=>' => \&_cmp,
39             'cmp' => \&_cmp,
40 30     30   529 '<' => sub { _cmp(@_[0,1], '<') == -1 },
41 26     26   924 '>' => sub { _cmp(@_[0,1], '>') == 1 },
42 1     1   341 '!=' => sub { _cmp(@_[0,1], '!=') != 0 },
43 48     48   1414 '==' => sub { _cmp(@_[0,1], '==') == 0 },
44 0     0   0 '+' => sub { $_[0] },
45 909     909   42734 '""' => sub { $_[0]->sse },
46 36     36   206 ;
  36         83  
  36         673  
47              
48             my %INSIDE_OUT_DATES;
49              
50             =head1 METHODS
51              
52             Beyond the methods documented below, this class inherits methods from the
53             L<RDF::Query::Node> and L<RDF::Trine::Node::Literal> classes.
54              
55             =over 4
56              
57             =cut
58              
59             sub _cmp {
60 138     138   675 my $nodea = shift;
61 138         198 my $nodeb = shift;
62 138         259 my $op = shift;
63            
64 138         485 my $l = Log::Log4perl->get_logger("rdf.query.node.literal");
65 138         6651 $l->debug('literal comparison: ' . Dumper($nodea, $nodeb));
66            
67 138 50       11630 return 1 unless blessed($nodeb);
68 138 50       809 return -1 if ($nodeb->isa('RDF::Trine::Node::Nil'));
69 138 100       633 return 1 if ($nodeb->isa('RDF::Query::Node::Blank'));
70 137 50       585 return 1 if ($nodeb->isa('RDF::Query::Node::Resource'));
71 137 50       514 return 1 unless ($nodeb->isa('RDF::Query::Node::Literal'));
72            
73 137   100     396 my $dta = $nodea->literal_datatype || '';
74 137   100     1053 my $dtb = $nodeb->literal_datatype || '';
75 137         762 my $datetype = '^http://www.w3.org/2001/XMLSchema#dateTime';
76 137   66     891 my $datecmp = ($dta =~ $datetype and $dtb =~ $datetype);
77 137   66     386 my $numericcmp = ($nodea->is_numeric_type and $nodeb->is_numeric_type);
78            
79 137 100       966 if ($datecmp) {
80 7         31 $l->trace('datecmp');
81 7         75 my $datea = $nodea->datetime;
82 7         26 my $dateb = $nodeb->datetime;
83 7 50 33     46 if ($datea and $dateb) {
84 7         780 my $cmp = eval { DateTime->compare_ignore_floating( $datea, $dateb ) };
  7         57  
85 7 50       479 return $cmp unless ($@);
86             }
87             }
88            
89 130 100       293 if ($numericcmp) {
90 53         186 $l->trace('both numeric cmp');
91 53 100       499 return 0 if ($nodea->equal( $nodeb )); # if the nodes are identical, return true (even if the lexical values don't appear to be numeric). i.e., "xyz"^^xsd:integer should equal itself, even though it's not a valid integer.
92 43         834 return $nodea->numeric_value <=> $nodeb->numeric_value;
93             }
94            
95             {
96 77         104 $l->trace('other cmp');
  77         221  
97            
98 77 50 66     606 if ($nodea->has_language and $nodeb->has_language) {
    50 33        
    50 66        
    100 33        
    100 33        
      100        
      66        
99 0         0 $l->trace('both have language');
100 0         0 my $lc = lc($nodea->literal_value_language) cmp lc($nodeb->literal_value_language);
101 0         0 my $vc = $nodea->literal_value cmp $nodeb->literal_value;
102 0         0 my $c;
103 0 0 0     0 if ($LAZY_COMPARISONS and ($lc != 0)) {
    0          
104 0   0     0 $c = ($vc || $lc);
105             } elsif ($lc == 0) {
106 0         0 $c = $vc;
107             } else {
108 0         0 $l->debug("Attempt to compare literals with differing languages.");
109 0         0 throw RDF::Query::Error::TypeError -text => "Attempt to compare literals with differing languages.";
110             }
111 0         0 $l->trace("-> $c");
112 0         0 return $c;
113             } elsif (($nodea->has_datatype and $dta eq 'http://www.w3.org/2001/XMLSchema#string') or ($nodeb->has_datatype and $dtb eq 'http://www.w3.org/2001/XMLSchema#string')) {
114 0         0 $l->trace("one is xsd:string");
115 36     36   19981 no warnings 'uninitialized';
  36         74  
  36         46783  
116             my ($na, $nb) = sort {
117 0 0 0     0 (blessed($b) and $b->isa('RDF::Query::Node::Literal'))
  0 0       0  
118             ? $b->literal_datatype eq 'http://www.w3.org/2001/XMLSchema#string'
119             : ($LAZY_COMPARISONS)
120             ? refaddr($a) <=> refaddr($b)
121             : throw RDF::Query::Error::TypeError -text => "Attempt to compare xsd:string with non-literal";
122             } ($nodea, $nodeb);
123            
124 0         0 my $c;
125 0 0 0     0 if ($nb->has_language) {
    0          
    0          
126 0         0 $c = -1;
127             } elsif (not($nb->has_datatype) or $nb->literal_datatype eq 'http://www.w3.org/2001/XMLSchema#string') {
128 0         0 $c = $nodea->literal_value cmp $nodeb->literal_value;
129             } elsif ($LAZY_COMPARISONS) {
130 0         0 return $nodea->as_string cmp $nodeb->as_string;
131             } else {
132 0         0 throw RDF::Query::Error::TypeError -text => "Attempt to compare typed-literal with xsd:string.";
133             }
134 0         0 $l->trace("-> $c");
135 0         0 return $c;
136             } elsif ($nodea->has_datatype and $nodeb->has_datatype) {
137 0         0 $l->trace("both have datatype");
138 0         0 my $dc = $nodea->literal_datatype cmp $nodeb->literal_datatype;
139 0         0 my $vc = $nodea->literal_value cmp $nodeb->literal_value;
140 0         0 my $c;
141            
142 0 0       0 if ($op eq '!=') {
143 0         0 throw RDF::Query::Error::TypeError -text => "Attempt to compare (neq) literals with unrecognized datatypes.";
144             } else {
145 0 0       0 if ($LAZY_COMPARISONS) {
    0          
146 0   0     0 $c = ($vc || $dc);
147             } elsif ($dc == 0) {
148 0         0 $c = $vc;
149             } else {
150 0         0 $l->debug("Attempt to compare literals with different datatypes.");
151 0         0 throw RDF::Query::Error::TypeError -text => "Attempt to compare literals with differing datatypes.";
152             }
153 0         0 $l->trace("-> $c");
154 0         0 return $c;
155             }
156             } elsif ($nodea->has_language or $nodeb->has_language) {
157 3         139 $l->trace("one has language");
158 3 100       25 my $c = ($nodea->has_language) ? 1 : -1;
159 3         33 $l->trace("-> $c");
160 3         26 return $c;
161             } elsif ($nodea->has_datatype or $nodeb->has_datatype) {
162 1         65 $l->trace("one has datatype");
163 1 50       9 if ($LAZY_COMPARISONS) {
164 0 0       0 my $c = ($nodea->has_datatype) ? 1 : -1;
165 0         0 $l->trace("-> $c");
166 0         0 return $c;
167             } else {
168 1         5 $l->debug("Attempt to compare typed-literal with plain-literal");
169 1         22 throw RDF::Query::Error::TypeError -text => "Attempt to compare typed-literal with plain-literal";
170             }
171             } else {
172 73         4287 $l->trace("something else");
173 73         530 my $vcmp = $nodea->literal_value cmp $nodeb->literal_value;
174 73         781 $l->trace("-> $vcmp");
175 73         781 return $vcmp;
176             }
177             }
178             }
179              
180             =item C<< datetime >>
181              
182             Returns a DateTime object from the literal if the literal value is in W3CDTF format.
183              
184             =cut
185              
186             sub datetime {
187 14     14 1 22 my $self = shift;
188 14         43 my $addr = refaddr( $self );
189 14 100       47 if (exists($INSIDE_OUT_DATES{ $addr })) {
190 1         4 return $INSIDE_OUT_DATES{ $addr };
191             } else {
192 13         48 my $value = $self->literal_value;
193 13         134 my $f = DateTime::Format::W3CDTF->new;
194 13         75 my $dt = eval { $f->parse_datetime( $value ) };
  13         55  
195 13         6113 $INSIDE_OUT_DATES{ $addr } = $dt;
196 13         58 return $dt;
197             }
198             }
199              
200             =item C<< as_sparql >>
201              
202             Returns the SPARQL string for this node.
203              
204             =cut
205              
206             sub as_sparql {
207 60     60 1 602 my $self = shift;
208 60 100       208 if ($self->is_numeric_type) {
209 7         27 return $self->literal_value;
210             } else {
211 53         626 return $self->sse;
212             }
213             }
214              
215             =item C<< as_hash >>
216              
217             Returns the query as a nested set of plain data structures (no objects).
218              
219             =cut
220              
221             sub as_hash {
222 0     0 1 0 my $self = shift;
223 0         0 my $context = shift;
224 0         0 my $hash = {
225             type => 'node',
226             literal => $self->literal_value,
227             };
228 0 0       0 $hash->{ language } = $self->literal_value_language if ($self->has_language);
229 0 0       0 $hash->{ datatype } = $self->literal_datatype if ($self->has_datatype);
230 0         0 return $hash;
231             }
232              
233             =item C<< is_simple_literal >>
234              
235             Returns true if the literal is "simple" -- is a literal without datatype or language.
236              
237             =cut
238              
239             sub is_simple_literal {
240 0     0 1 0 my $self = shift;
241 0   0     0 return not($self->has_language or $self->has_datatype);
242             }
243              
244             =item C<< is_numeric_type >>
245              
246             Returns true if the literal is a known (xsd) numeric type.
247              
248             =cut
249              
250             sub is_numeric_type {
251 705     705 1 1504 my $self = shift;
252 705 100       2192 return 0 unless ($self->has_datatype);
253 542         4853 my $type = $self->literal_datatype;
254 542 100       5716 if ($type =~ qr<^http://www.w3.org/2001/XMLSchema#(integer|decimal|float|double|non(Positive|Negative)Integer|(positive|negative)Integer|long|int|short|byte|unsigned(Long|Int|Short|Byte))>) {
255 406         1499 return 1;
256             } else {
257 136         688 return 0;
258             }
259             }
260              
261             =item C<< numeric_value >>
262              
263             Returns the numeric value of the literal (even if the literal isn't a known numeric type.
264              
265             =cut
266              
267             sub numeric_value {
268 191     191 1 10899 my $self = shift;
269 191 100       408 if ($self->is_numeric_type) {
    100          
    50          
270 187         529 my $value = $self->literal_value;
271 187 50       1372 if (looks_like_number($value)) {
272 187         10682 my $v = 0 + eval "$value";
273 187         1041 return $v;
274             } else {
275 0         0 throw RDF::Query::Error::TypeError -text => "Literal with numeric type does not appear to have numeric value.";
276             }
277             } elsif (not $self->has_datatype) {
278 1 50       23 if (looks_like_number($self->literal_value)) {
279 1         11 return 0+$self->literal_value;
280             } else {
281 0         0 return;
282             }
283             } elsif ($self->literal_datatype eq 'http://www.w3.org/2001/XMLSchema#boolean') {
284 3 100       41 return ($self->literal_value eq 'true') ? 1 : 0;
285             } else {
286 0         0 return;
287             }
288             }
289              
290             =item C<< type_list >>
291              
292             Returns a two-item list suitable for use as the second and third arguments to
293             RDF::Query::Node::Literal constructor. The two returned values correspond to
294             literal language tag and literal datatype URI, respectively.
295              
296             =cut
297              
298             sub type_list {
299 0     0 1 0 my $self = shift;
300 0         0 return ($self->literal_value_language, $self->literal_datatype);
301             }
302              
303             sub DESTROY {
304 941     941   110964 my $self = shift;
305 941         2031 my $addr = refaddr($self);
306 941         4811 delete $INSIDE_OUT_DATES{ $addr };
307             }
308              
309              
310             1;
311              
312             __END__
313              
314             =back
315              
316             =head1 AUTHOR
317              
318             Gregory Todd Williams <gwilliams@cpan.org>
319              
320             =cut