File Coverage

blib/lib/AtteanX/Serializer/RDFXML.pm
Criterion Covered Total %
statement 121 133 90.9
branch 25 34 73.5
condition n/a
subroutine 15 15 100.0
pod 2 2 100.0
total 163 184 88.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             AtteanX::Serializer::RDFXML - RDF/XML Serializer
4              
5             =head1 VERSION
6              
7             This document describes AtteanX::Serializer::RDFXML version 0.032
8              
9             =head1 SYNOPSIS
10              
11             use Attean;
12             my $s = Attean->get_serializer('RDFXML')->new();
13             $s->serialize_iter_to_io( $fh, $iter );
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<< scoped_namespaces >>
26              
27             =item C<< file_extensions >>
28              
29             =item C<< file_extensions >>
30              
31             =back
32              
33             =head1 METHODS
34              
35             =over 4
36              
37             =cut
38              
39 4     4   17226 use v5.14;
  4         13  
40 4     4   21 use warnings;
  4         8  
  4         162  
41              
42             use Moo;
43 4     4   24 use Types::Standard qw(Str ArrayRef HashRef);
  4         7  
  4         26  
44 4     4   1329 use Encode qw(encode);
  4         10  
  4         40  
45 4     4   2954 use Scalar::Util qw(blessed);
  4         8  
  4         185  
46 4     4   21 use Attean::ListIterator;
  4         7  
  4         142  
47 4     4   21 use List::MoreUtils qw(any);
  4         13  
  4         115  
48 4     4   19 use namespace::clean;
  4         9  
  4         28  
49 4     4   3566  
  4         7  
  4         30  
50             has 'canonical_media_type' => (is => 'ro', isa => Str, init_arg => undef, default => 'application/rdf+xml');
51             has '_rev' => (is => 'rw', isa => HashRef, init_arg => undef, default => sub { +{} });
52             has 'scoped_namespaces' => (is => 'rw', init_arg => undef);
53              
54             =item C<< file_extensions >>
55              
56             Returns a list of file extensions associated with the serialized format.
57              
58             =cut
59              
60            
61 16     16 1 1088 =item C<< media_types >>
62              
63             Returns a list of media types that identify the format produced by this serializer.
64              
65             =cut
66              
67             return [qw(application/rdf+xml)];
68             }
69            
70 11     11 1 1812 =item C<< serialize_iter_to_io( $fh, $iterator ) >>
71              
72             Serializes the L<Attean::API::Triple> objects from C<< $iterator >> to the
73             L<IO::Handle> object C<< $fh >> (which SHOULD be open with the UTF-8 encoding).
74              
75             =cut
76              
77             my $self = shift;
78             my $io = shift;
79             my $iter = shift;
80            
81             my $ns = $self->_top_xmlns();
82             my $base_uri = '';
83             if ($self->{base_uri}) {
84             $base_uri = "xml:base=\"$self->{base_uri}\" ";
85             }
86             print {$io} qq[<?xml version="1.0" encoding="utf-8"?>\n<rdf:RDF ${base_uri}xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"$ns>\n];
87            
88             my $st = $iter->next;
89             my @statements;
90             push(@statements, $st) if blessed($st);
91             while (@statements) {
92             my $st = shift(@statements);
93             my @samesubj;
94             push(@samesubj, $st);
95             my $subj = $st->subject;
96             while (my $row = $iter->next) {
97             if ($row->subject->equals( $subj )) {
98             push(@samesubj, $row);
99             } else {
100             push(@statements, $row);
101             last;
102             }
103             }
104            
105             print {$io} $self->_statements_same_subject_as_string( @samesubj );
106             }
107            
108             print {$io} qq[</rdf:RDF>\n];
109             return;
110             }
111            
112             =item C<< serialize_iter_to_bytes( $iterator ) >>
113              
114             Serializes the L<Attean::API::Triple> objects from C<< $iterator >>
115             and returns the serialization as a UTF-8 encoded byte string.
116              
117             =cut
118              
119             my $self = shift;
120             my $iter = shift;
121             my $data = '';
122             open(my $fh, '>:utf8', \$data);
123             $self->serialize_iter_to_io($fh, $iter);
124             close($fh);
125             return $data;
126             }
127 1     1   7  
  1         3  
  1         7  
128             my $self = shift;
129             my @statements = @_;
130             my $s = $statements[0]->subject;
131            
132             my $id;
133             if ($s->does('Attean::API::Blank')) {
134 12     12   14 my $b = 'b' . $s->value;
135 12         25 $id = qq[rdf:nodeID="$b"];
136 12         25 } else {
137             my $i = $s->abs;
138 12         20 for ($i) {
139 12 100       37 s/&/&amp;/g;
140 10         173 s/</&lt;/g;
141 10         22 s/"/&quot;/g;
142             }
143 2         80 $id = qq[rdf:about="$i"];
144 2         275 }
145 2         5
146 2         5 my $counter = 1;
147 2         5 my %namespaces = %{ $self->_rev };
148             my $string = '';
149 2         6 foreach my $st (@statements) {
150             my (undef, $p, $o) = $st->values;
151             my %used_namespaces;
152 12         20 my ($ns, $ln);
153 12         18 eval {
  12         179  
154 12         83 ($ns,$ln) = $self->_qname($p);
155 12         21 };
156 24         69 if ($@) {
157 24         42 my $uri = $p->abs;
158 24         33 die "Can't turn predicate $uri into a QName.";
159 24         34 }
160 24         52 $used_namespaces{ $ns }++;
161             unless (exists $namespaces{ $ns }) {
162 24 50       54 $namespaces{ $ns } = 'ns' . $counter++;
163 0         0 }
164 0         0
165             my $prefix = $namespaces{ $ns };
166 24         63 my $nsdecl = '';
167 24 100       58 if ($self->scoped_namespaces) {
168 11         33 $nsdecl = qq[ xmlns:$prefix="$ns"];
169             }
170             if ($o->does('Attean::API::Literal')) {
171 24         39 my $lv = $o->value;
172 24         31 for ($lv) {
173 24 50       65 s/&/&amp;/g;
174 0         0 s/</&lt;/g;
175             s/"/&quot;/g;
176 24 100       69 }
    50          
177 22         357 my $lang = $o->language;
178 22         42 my $dt = $o->datatype->value;
179 22         37 my $tag = join(':', $prefix, $ln);
180 22         35
181 22         34 if ($lang) {
182             $string .= qq[\t<${tag}${nsdecl} xml:lang="${lang}">${lv}</${tag}>\n];
183 22         43 } elsif ($dt) {
184 22         390 if ($dt eq 'http://www.w3.org/2001/XMLSchema#string') {
185 22         512 $string .= qq[\t<${tag}${nsdecl}>${lv}</${tag}>\n];
186             } else {
187 22 100       49 $string .= qq[\t<${tag}${nsdecl} rdf:datatype="${dt}">${lv}</${tag}>\n];
    50          
188 2         13 }
189             } else {
190 20 100       34 $string .= qq[\t<${tag}${nsdecl}>${lv}</${tag}>\n];
191 4         18 }
192             } elsif ($o->does('Attean::API::Blank')) {
193 16         60 my $b = 'b' . $o->value;
194             for ($b) {
195             s/&/&amp;/g;
196 0         0 s/</&lt;/g;
197             s/"/&quot;/g;
198             }
199 0         0 $string .= qq[\t<${prefix}:$ln${nsdecl} rdf:nodeID="$b"/>\n];
200 0         0 } else {
201 0         0 my $u = $o->abs;
202 0         0 for ($u) {
203 0         0 s/&/&amp;/g;
204             s/</&lt;/g;
205 0         0 s/"/&quot;/g;
206             }
207 2         111 $string .= qq[\t<${prefix}:$ln${nsdecl} rdf:resource="$u"/>\n];
208 2         417 }
209 2         4 }
210 2         4
211 2         5 $string .= qq[</rdf:Description>\n];
212            
213 2         10 # rdf namespace is already defined in the <rdf:RDF> tag, so ignore it here
214             my %seen = %{ $self->_rev };
215             my @ns;
216             foreach my $uri (sort { $namespaces{$a} cmp $namespaces{$b} } grep { not($seen{$_}) } (keys %namespaces)) {
217 12         20 my $ns = $namespaces{$uri};
218             my $str = ($ns eq '') ? qq[xmlns="$uri"] : qq[xmlns:${ns}="$uri"];
219             push(@ns, $str);
220 12         17 }
  12         170  
221 12         78 my $ns = join(' ', @ns);
222 12         27 if ($ns) {
  0         0  
  16         52  
223 11         18 return qq[<rdf:Description ${ns} $id>\n] . $string;
224 11 50       31 } else {
225 11         24 return qq[<rdf:Description $id>\n] . $string;
226             }
227 12         24 }
228 12 100       27  
229 11         83 my $self = shift;
230             my $p = shift;
231 1         8 my $uri = $p->abs;
232              
233             state $r_PN_CHARS_BASE = qr/([A-Z]|[a-z]|[\x{00C0}-\x{00D6}]|[\x{00D8}-\x{00F6}]|[\x{00F8}-\x{02FF}]|[\x{0370}-\x{037D}]|[\x{037F}-\x{1FFF}]|[\x{200C}-\x{200D}]|[\x{2070}-\x{218F}]|[\x{2C00}-\x{2FEF}]|[\x{3001}-\x{D7FF}]|[\x{F900}-\x{FDCF}]|[\x{FDF0}-\x{FFFD}]|[\x{10000}-\x{EFFFF}])/o;
234             state $r_PN_CHARS_U = qr/(_|${r_PN_CHARS_BASE})/o;
235             state $r_PN_CHARS = qr/${r_PN_CHARS_U}|-|[0-9]|\x{00B7}|[\x{0300}-\x{036F}]|[\x{203F}-\x{2040}]/o;
236 24     24   33 state $r_PN_LOCAL = qr/((${r_PN_CHARS_U})((${r_PN_CHARS}|[.])*${r_PN_CHARS})?)/o;
237 24         60 if ($uri =~ m/${r_PN_LOCAL}$/o) {
238 24         424 my $ln = $1;
239             my $ns = substr($uri, 0, length($uri)-length($ln));
240 24         2343 return ($ns, $ln);
241 24         118 } else {
242 24         90 die "Can't turn IRI $uri into a QName.";
243 24         165 }
244 24 50       2554 }
245 24         50  
246 24         54 my $self = shift;
247 24         78 my $namespaces = $self->namespaces;
248             return '' if ($self->scoped_namespaces);
249 0         0
250             my @ns;
251             my @prefixes = $namespaces ? $namespaces->list_prefixes : ();
252             foreach my $k (sort { $a cmp $b } @prefixes) {
253             my $v = $namespaces->namespace_uri($k)->as_string;
254 10     10   15 $self->_rev->{$v} = $k;
255 10         26 next if ($v eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
256 10 50       31 my $str = ($k eq '') ? qq[xmlns="$v"] : qq[xmlns:$k="$v"];
257             push(@ns, $str);
258 10         14 }
259 10 100       37 my $ns = join(' ', @ns);
260 10         47 if (length($ns)) {
  1         4  
261 5         19 $ns = " $ns";
262 5         953 }
263 5 50       43 return $ns;
264 5 50       22 }
265 5         13  
266             with 'Attean::API::TripleSerializer';
267 10         20 with 'Attean::API::AbbreviatingSerializer';
268 10 100       38 }
269 4         10  
270             1;
271 10         25  
272              
273             =back
274              
275             =head1 BUGS
276              
277             Please report any bugs or feature requests to through the GitHub web interface
278             at L<https://github.com/kasei/perlrdf/issues>.
279              
280             =head1 SEE ALSO
281              
282             L<http://www.w3.org/TR/rdf-syntax-grammar/>
283              
284             =head1 AUTHOR
285              
286             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
287              
288             =head1 COPYRIGHT
289              
290             Copyright (c) 2014--2022 Gregory Todd Williams. This
291             program is free software; you can redistribute it and/or modify it under
292             the same terms as Perl itself.
293              
294             =cut