File Coverage

blib/lib/AtteanX/Serializer/SPARQL.pm
Criterion Covered Total %
statement 37 37 100.0
branch n/a
condition n/a
subroutine 15 15 100.0
pod 4 4 100.0
total 56 56 100.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             AtteanX::Serializer::SPARQL - SPARQL Serializer
4              
5             =head1 VERSION
6              
7             This document describes AtteanX::Serializer::SPARQL version 0.032
8              
9             =head1 SYNOPSIS
10              
11             use Attean;
12             my $serializer = Attean->get_serializer('SPARQL')->new();
13             $serializer->serialize_iter_to_io( $io, $fh );
14              
15             =head1 DESCRIPTION
16              
17             ...
18              
19             =head1 ATTRIBUTES
20              
21             =over 4
22              
23             =item C<< canonical_media_type >>
24              
25             =item C<< media_types >>
26              
27             =item C<< handled_type >>
28              
29             =item C<< file_extensions >>
30              
31             =back
32              
33             =head1 METHODS
34              
35             =over 4
36              
37             =cut
38              
39 50     50   609 use v5.14;
  50         154  
40 50     50   249 use warnings;
  50         97  
  50         2160  
41              
42             use Moo;
43 50     50   310 use Data::Dumper;
  50         111  
  50         303  
44 50     50   17070 use Encode qw(encode);
  50         155  
  50         2999  
45 50     50   372 use Attean::ListIterator;
  50         116  
  50         2074  
46 50     50   17799 use Scalar::Util qw(blessed);
  50         140  
  50         1439  
47 50     50   321 use List::MoreUtils qw(any);
  50         117  
  50         2189  
48 50     50   260 use AtteanX::SPARQL::Constants;
  50         117  
  50         533  
49 50     50   47173 use namespace::clean;
  50         108  
  50         7563  
50 50     50   302 with 'Attean::API::AbbreviatingSerializer';
  50         106  
  50         242  
51            
52              
53 1     1 1 3651 return [qw(application/sparql-query)];
54             }
55              
56 11     11 1 30 =item C<< file_extensions >>
57              
58             Returns a list of file extensions associated with the serialized format.
59              
60             =cut
61              
62            
63             state $ITEM_TYPE = Type::Tiny::Role->new(role => 'AtteanX::SPARQL::Token');
64             return $ITEM_TYPE;
65 13     13 1 1122 }
66              
67             =item C<< serialize_iter_to_io( $fh, $iterator ) >>
68 1     1 1 372  
69 1         160 Serializes the SPARQL token objects from C<< $iterator >> to the
70             L<IO::Handle> object C<< $fh >>.
71              
72             =cut
73              
74             my $self = shift;
75             my $io = shift;
76             my $iter = shift;
77             my $indent = 0;
78             my $newline = 1;
79             my $semicolon = 0;
80             my $need_space = 0;
81             my $ns = $self->namespaces;
82             my $parser = Attean->get_parser('SPARQLLex')->new();
83            
84             if ($ns) {
85             NSLOOP: foreach my $p (sort $ns->list_prefixes) {
86             my $prefix = $ns->namespace_uri($p)->as_string;
87             $io->print("PREFIX $p: <$prefix>\n");
88             }
89             }
90            
91             my $last;
92            
93             while (my $t = $iter->next()) {
94             my $type = $t->type;
95            
96             if ($type == LANG or $type == HATHAT) {
97             $need_space= 0;
98             }
99            
100             unless ($newline) {
101             if ($type == RBRACE) {
102             $io->print("\n");
103             $newline = 1;
104             } elsif ($type == KEYWORD and $t->value =~ /^(BASE|PREFIX|SELECT|ASK|CONSTRUCT|DESCRIBE|USING|FROM)$/) {
105             $io->print("\n");
106             $newline = 1;
107             } elsif ($type == KEYWORD and $t->value eq 'WHERE' and blessed($last) and ($last->type == PREFIXNAME or $last->type == IRI)) {
108             # this captures "USING <g> WHERE" and "USING NAMED <g> WHERE", forcing a newline before the "WHERE"
109             $io->print("\n");
110             $newline = 1;
111             }
112             }
113            
114             if ($type == RBRACE) {
115             $indent--;
116             }
117            
118             if ($semicolon and $type == KEYWORD and $t->value =~ /^(BASE|PREFIX|SELECT|ADD|COPY|MOVE|USING|LOAD|DELETE|INSERT|WITH|CLEAR|DROP|CREATE)$/) {
119             # SPARQL Update use of a semicolon is different from its use in a Query;
120             # In queries, semicolon affects indentation. In updates, it's just a separator.
121             # So back out the indentation if it's being used as a separator here.
122             $semicolon = 0;
123             $indent--;
124             }
125            
126             if ($newline) {
127             $io->print(' ' x $indent);
128             $newline = 0;
129             } elsif ($need_space) {
130             $io->print(' ');
131             $need_space = 0;
132             }
133            
134             if ($type == KEYWORD) {
135             $io->print($t->value);
136             $need_space++;
137             } elsif ($type == IRI) {
138             my $value = $t->value;
139             my $ser = '<' . $value . '>';
140             if ($ns) {
141             NSLOOP: foreach my $p ($ns->list_prefixes) {
142             my $prefix = $ns->namespace_uri($p)->as_string;
143             if (substr($value, 0, length($prefix)) eq $prefix) {
144             # now verify that the prefixname is valid SPARQL syntax by re-parsing it
145             my $pname = join(':', $p, substr($value, length($prefix)));
146             my $b = $pname;
147             $b = encode('UTF-8', $b, Encode::FB_CROAK);
148             my ($pnt) = eval { $parser->parse_list_from_bytes($b) };
149             if (blessed($pnt) and $pnt->type == PREFIXNAME) {
150             $ser = $pname;
151             }
152             last NSLOOP;
153             }
154             }
155             }
156            
157             # TODO: escape
158             $io->print($ser);
159             $need_space++;
160             } elsif ($type == PREFIXNAME) {
161             my $args = $t->args;
162             $io->print(join('', @$args));
163             $need_space++;
164             } elsif ($type == BNODE) {
165             $io->print('_:');
166             $io->print($t->value);
167             $need_space++;
168             } elsif ($type == LANG) {
169             $io->print('@');
170             $io->print($t->value);
171             $need_space++;
172             } elsif ($type == STRING1S) {
173             my $value = $t->value;
174             $value =~ s/'/\\'/g;
175             $io->print("'");
176             $io->print($value);
177             $io->print("'");
178             $need_space++;
179             } elsif ($type == STRING1D) {
180             my $value = $t->value;
181             $value =~ s/"/\\"/g;
182             $io->print('"');
183             $io->print($value);
184             $io->print('"');
185             $need_space++;
186             } elsif ($type == STRING3S) {
187             my $value = $t->value;
188             $value =~ s/'''/''\\'/g;
189             $io->print("'''");
190             $io->print($value);
191             $io->print("'''");
192             $need_space++;
193             } elsif ($type == STRING3D) {
194             my $value = $t->value;
195             $value =~ s/"""/""\\"/g;
196             $io->print('"""');
197             $io->print($value);
198             $io->print('"""');
199             $need_space++;
200             } elsif ($type == VAR) {
201             $io->print('?');
202             $io->print($t->value);
203             $need_space++;
204             } elsif ($type == A) {
205             $io->print('a');
206             $need_space++;
207             } elsif ($type == WS) {
208             } elsif ($type == COMMENT) {
209             $io->print('# ');
210             $io->print($t->value);
211             $io->print("\n");
212             } elsif ($type == HATHAT) {
213             $io->print($t->value);
214             } else {
215             $io->print($t->value);
216             $need_space++;
217             }
218            
219             if ($type == DOT) {
220             if ($semicolon) {
221             $indent--;
222             $semicolon = 0;
223             }
224             $need_space = 0;
225             $io->print("\n");
226             $newline = 1;
227             } elsif ($type == LBRACE) {
228             $io->print("\n");
229             $need_space = 0;
230             $newline = 1;
231             $indent++;
232             } elsif ($type == SEMICOLON) {
233             $io->print("\n");
234             $need_space = 0;
235             $newline = 1;
236             unless ($semicolon) {
237             $indent++;
238             }
239             $semicolon = 1;
240             }
241             $last = $t;
242             }
243             unless ($newline) {
244             $io->print("\n");
245             }
246             return;
247             }
248            
249             =item C<< serialize_iter_to_bytes( $iterator ) >>
250              
251             Serializes the SPARQL token objects from C<< $iterator >>
252             and returns the serialization as a UTF-8 encoded byte string.
253              
254             =cut
255              
256             my $self = shift;
257             my $iter = shift;
258             my $data = '';
259             open(my $fh, '>:utf8', \$data);
260             $self->serialize_iter_to_io($fh, $iter);
261             close($fh);
262             return $data;
263             }
264             }
265              
266 2     2   16 1;
  2         5  
  2         22  
267              
268              
269             =back
270              
271             =head1 BUGS
272              
273             Please report any bugs or feature requests to through the GitHub web interface
274             at L<https://github.com/kasei/perlrdf/issues>.
275              
276             =head1 AUTHOR
277              
278             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
279              
280             =head1 COPYRIGHT
281              
282             Copyright (c) 2014--2022 Gregory Todd Williams. This
283             program is free software; you can redistribute it and/or modify it under
284             the same terms as Perl itself.
285              
286             =cut