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   17425 use v5.14;
  4         14  
40 4     4   22 use warnings;
  4         9  
  4         163  
41              
42             use Moo;
43 4     4   22 use Types::Standard qw(Str ArrayRef HashRef);
  4         7  
  4         24  
44 4     4   1326 use Encode qw(encode);
  4         10  
  4         45  
45 4     4   2880 use Scalar::Util qw(blessed);
  4         10  
  4         183  
46 4     4   24 use Attean::ListIterator;
  4         8  
  4         166  
47 4     4   28 use List::MoreUtils qw(any);
  4         18  
  4         88  
48 4     4   18 use namespace::clean;
  4         9  
  4         30  
49 4     4   3726  
  4         11  
  4         33  
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 1128 =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 2016 =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   8  
  1         1  
  1         8  
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   17 my $b = 'b' . $s->value;
135 12         25 $id = qq[rdf:nodeID="$b"];
136 12         25 } else {
137             my $i = $s->abs;
138 12         25 for ($i) {
139 12 100       39 s/&/&amp;/g;
140 10         190 s/</&lt;/g;
141 10         18 s/"/&quot;/g;
142             }
143 2         100 $id = qq[rdf:about="$i"];
144 2         279 }
145 2         7
146 2         6 my $counter = 1;
147 2         5 my %namespaces = %{ $self->_rev };
148             my $string = '';
149 2         7 foreach my $st (@statements) {
150             my (undef, $p, $o) = $st->values;
151             my %used_namespaces;
152 12         22 my ($ns, $ln);
153 12         16 eval {
  12         194  
154 12         92 ($ns,$ln) = $self->_qname($p);
155 12         30 };
156 24         79 if ($@) {
157 24         45 my $uri = $p->abs;
158 24         38 die "Can't turn predicate $uri into a QName.";
159 24         34 }
160 24         62 $used_namespaces{ $ns }++;
161             unless (exists $namespaces{ $ns }) {
162 24 50       62 $namespaces{ $ns } = 'ns' . $counter++;
163 0         0 }
164 0         0
165             my $prefix = $namespaces{ $ns };
166 24         57 my $nsdecl = '';
167 24 100       62 if ($self->scoped_namespaces) {
168 11         37 $nsdecl = qq[ xmlns:$prefix="$ns"];
169             }
170             if ($o->does('Attean::API::Literal')) {
171 24         44 my $lv = $o->value;
172 24         35 for ($lv) {
173 24 50       73 s/&/&amp;/g;
174 0         0 s/</&lt;/g;
175             s/"/&quot;/g;
176 24 100       81 }
    50          
177 22         419 my $lang = $o->language;
178 22         40 my $dt = $o->datatype->value;
179 22         48 my $tag = join(':', $prefix, $ln);
180 22         39
181 22         33 if ($lang) {
182             $string .= qq[\t<${tag}${nsdecl} xml:lang="${lang}">${lv}</${tag}>\n];
183 22         52 } elsif ($dt) {
184 22         415 if ($dt eq 'http://www.w3.org/2001/XMLSchema#string') {
185 22         569 $string .= qq[\t<${tag}${nsdecl}>${lv}</${tag}>\n];
186             } else {
187 22 100       61 $string .= qq[\t<${tag}${nsdecl} rdf:datatype="${dt}">${lv}</${tag}>\n];
    50          
188 2         11 }
189             } else {
190 20 100       38 $string .= qq[\t<${tag}${nsdecl}>${lv}</${tag}>\n];
191 4         22 }
192             } elsif ($o->does('Attean::API::Blank')) {
193 16         70 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         146 $string .= qq[\t<${prefix}:$ln${nsdecl} rdf:resource="$u"/>\n];
208 2         506 }
209 2         4 }
210 2         5
211 2         7 $string .= qq[</rdf:Description>\n];
212            
213 2         9 # 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         28 my $ns = $namespaces{$uri};
218             my $str = ($ns eq '') ? qq[xmlns="$uri"] : qq[xmlns:${ns}="$uri"];
219             push(@ns, $str);
220 12         17 }
  12         181  
221 12         81 my $ns = join(' ', @ns);
222 12         30 if ($ns) {
  0         0  
  16         48  
223 11         48 return qq[<rdf:Description ${ns} $id>\n] . $string;
224 11 50       41 } else {
225 11         25 return qq[<rdf:Description $id>\n] . $string;
226             }
227 12         32 }
228 12 100       23  
229 11         90 my $self = shift;
230             my $p = shift;
231 1         10 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   37 state $r_PN_LOCAL = qr/((${r_PN_CHARS_U})((${r_PN_CHARS}|[.])*${r_PN_CHARS})?)/o;
237 24         35 if ($uri =~ m/${r_PN_LOCAL}$/o) {
238 24         414 my $ln = $1;
239             my $ns = substr($uri, 0, length($uri)-length($ln));
240 24         2574 return ($ns, $ln);
241 24         124 } else {
242 24         97 die "Can't turn IRI $uri into a QName.";
243 24         169 }
244 24 50       2645 }
245 24         65  
246 24         65 my $self = shift;
247 24         80 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   21 $self->_rev->{$v} = $k;
255 10         29 next if ($v eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
256 10 50       36 my $str = ($k eq '') ? qq[xmlns="$v"] : qq[xmlns:$k="$v"];
257             push(@ns, $str);
258 10         16 }
259 10 100       53 my $ns = join(' ', @ns);
260 10         57 if (length($ns)) {
  1         6  
261 5         21 $ns = " $ns";
262 5         1154 }
263 5 50       49 return $ns;
264 5 50       24 }
265 5         15  
266             with 'Attean::API::TripleSerializer';
267 10         29 with 'Attean::API::AbbreviatingSerializer';
268 10 100       24 }
269 4         11  
270             1;
271 10         30  
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