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.918.
11              
12             =cut
13              
14             package RDF::Query::Node::Literal;
15              
16 36     36   134 use strict;
  36         48  
  36         843  
17 36     36   118 use warnings;
  36         42  
  36         786  
18 36     36   116 no warnings 'redefine';
  36         43  
  36         891  
19 36     36   118 use base qw(RDF::Query::Node RDF::Trine::Node::Literal);
  36         44  
  36         2876  
20              
21 36     36   1503 use DateTime;
  36         604916  
  36         725  
22 36     36   1076 use DateTime::Format::W3CDTF;
  36         1171  
  36         666  
23 36     36   899 use RDF::Query::Error;
  36         42  
  36         269  
24 36     36   1840 use Data::Dumper;
  36         57  
  36         1304  
25 36     36   134 use Log::Log4perl;
  36         53  
  36         241  
26 36     36   1374 use Scalar::Util qw(blessed refaddr looks_like_number);
  36         50  
  36         1605  
27 36     36   125 use Carp qw(carp croak confess);
  36         45  
  36         1992  
28              
29             ######################################################################
30              
31             our ($VERSION, $LAZY_COMPARISONS);
32             BEGIN {
33 36     36   4822 $VERSION = '2.918';
34             }
35              
36             ######################################################################
37              
38             use overload '<=>' => \&_cmp,
39             'cmp' => \&_cmp,
40 29     29   539 '<' => sub { _cmp(@_[0,1], '<') == -1 },
41 25     25   874 '>' => sub { _cmp(@_[0,1], '>') == 1 },
42 1     1   392 '!=' => sub { _cmp(@_[0,1], '!=') != 0 },
43 47     47   1287 '==' => sub { _cmp(@_[0,1], '==') == 0 },
44 0     0   0 '+' => sub { $_[0] },
45 905     905   35683 '""' => sub { $_[0]->sse },
46 36     36   160 ;
  36         46  
  36         467  
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 133     133   474 my $nodea = shift;
61 133         146 my $nodeb = shift;
62 133         155 my $op = shift;
63            
64 133         399 my $l = Log::Log4perl->get_logger("rdf.query.node.literal");
65 133         4658 $l->debug('literal comparison: ' . Dumper($nodea, $nodeb));
66            
67 133 50       8252 return 1 unless blessed($nodeb);
68 133 50       633 return -1 if ($nodeb->isa('RDF::Trine::Node::Nil'));
69 133 100       493 return 1 if ($nodeb->isa('RDF::Query::Node::Blank'));
70 132 50       429 return 1 if ($nodeb->isa('RDF::Query::Node::Resource'));
71 132 50       330 return 1 unless ($nodeb->isa('RDF::Query::Node::Literal'));
72            
73 132   100     339 my $dta = $nodea->literal_datatype || '';
74 132   100     761 my $dtb = $nodeb->literal_datatype || '';
75 132         544 my $datetype = '^http://www.w3.org/2001/XMLSchema#dateTime';
76 132   66     770 my $datecmp = ($dta =~ $datetype and $dtb =~ $datetype);
77 132   66     270 my $numericcmp = ($nodea->is_numeric_type and $nodeb->is_numeric_type);
78            
79 132 100       687 if ($datecmp) {
80 7         20 $l->trace('datecmp');
81 7         44 my $datea = $nodea->datetime;
82 7         18 my $dateb = $nodeb->datetime;
83 7 50 33     25 if ($datea and $dateb) {
84 7         450 my $cmp = eval { DateTime->compare_ignore_floating( $datea, $dateb ) };
  7         23  
85 7 50       313 return $cmp unless ($@);
86             }
87             }
88            
89 125 100       237 if ($numericcmp) {
90 50         143 $l->trace('both numeric cmp');
91 50 100       365 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 37         562 return $nodea->numeric_value <=> $nodeb->numeric_value;
93             }
94            
95             {
96 75         72 $l->trace('other cmp');
  75         174  
97            
98 75 50 66     426 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   13303 no warnings 'uninitialized';
  36         49  
  36         30862  
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         105 $l->trace("one has language");
158 3 100       15 my $c = ($nodea->has_language) ? 1 : -1;
159 3         23 $l->trace("-> $c");
160 3         21 return $c;
161             } elsif ($nodea->has_datatype or $nodeb->has_datatype) {
162 1         39 $l->trace("one has datatype");
163 1 50       6 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         2 $l->debug("Attempt to compare typed-literal with plain-literal");
169 1         18 throw RDF::Query::Error::TypeError -text => "Attempt to compare typed-literal with plain-literal";
170             }
171             } else {
172 71         2764 $l->trace("something else");
173 71         353 my $vcmp = $nodea->literal_value cmp $nodeb->literal_value;
174 71         581 $l->trace("-> $vcmp");
175 71         569 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 15 my $self = shift;
188 14         30 my $addr = refaddr( $self );
189 14 100       33 if (exists($INSIDE_OUT_DATES{ $addr })) {
190 1         2 return $INSIDE_OUT_DATES{ $addr };
191             } else {
192 13         34 my $value = $self->literal_value;
193 13         88 my $f = DateTime::Format::W3CDTF->new;
194 13         47 my $dt = eval { $f->parse_datetime( $value ) };
  13         30  
195 13         5015 $INSIDE_OUT_DATES{ $addr } = $dt;
196 13         41 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 62     62 1 469 my $self = shift;
208 62 100       176 if ($self->is_numeric_type) {
209 7         19 return $self->literal_value;
210             } else {
211 55         511 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 686     686 1 1141 my $self = shift;
252 686 100       1558 return 0 unless ($self->has_datatype);
253 523         3049 my $type = $self->literal_datatype;
254 523 100       4348 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 388         1161 return 1;
256             } else {
257 135         580 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 179     179 1 10021 my $self = shift;
269 179 100       260 if ($self->is_numeric_type) {
    100          
    50          
270 175         344 my $value = $self->literal_value;
271 175 50       936 if (looks_like_number($value)) {
272 175         7532 my $v = 0 + eval "$value";
273 175         770 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       16 if (looks_like_number($self->literal_value)) {
279 1         9 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       27 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 937     937   105582 my $self = shift;
305 937         1353 my $addr = refaddr($self);
306 937         3140 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