File Coverage

blib/lib/RDF/Prefixes.pm
Criterion Covered Total %
statement 94 123 76.4
branch 21 36 58.3
condition 17 35 48.5
subroutine 23 28 82.1
pod 13 13 100.0
total 168 235 71.4


line stmt bran cond sub pod time code
1             package RDF::Prefixes;
2              
3 4     4   100386 use 5.010;
  4         19  
  4         152  
4 4     4   22 use strict;
  4         11  
  4         186  
5             use constant {
6 4         509 IDX_USED => 0,
7             IDX_SUGGESTED => 1,
8             IDX_OPTIONS => 2,
9             NEXT_IDX => 3,
10 4     4   21 };
  4         11  
11 4     4   7727 use overload '%{}' => \&to_hashref;
  4         4531  
  4         36  
12 4     4   286 use overload '""' => \&to_string;
  4         7  
  4         21  
13 4     4   4240 use utf8;
  4         48  
  4         26  
14              
15             BEGIN {
16 4 50   4   465 eval 'use Carp qw(carp); 1'
  4     4   22  
  4         8  
  4         258  
17             or eval 'sub carp { warn "$_[0]\n" }'
18             }
19              
20             BEGIN {
21 4     4   10 $RDF::Prefixes::AUTHORITY = 'cpan:TOBYINK';
22 4         8094 $RDF::Prefixes::VERSION = '0.005';
23             }
24              
25             # These are the rules from Turtle (W3C WD, dated 09 Aug 2011).
26             # XML 1.0 5e's syntax for XML names (i.e. element names,
27             # attribute names, etc) appears to be pretty similar except
28             # that it allows names to start with a colon or full-stop.
29             # (The former would violate XML namespaces, but is allowed by
30             # XML itself - apparently.)
31             #
32             # So anyway, we go with Turtle as that is the more restrictive
33             # syntax, thus any valid Turtle names should automatically be
34             # valid XML names.
35             #
36              
37             my $PN_CHARS_BASE = qr<(?:
38             [A-Z]
39             | [a-z]
40             | [\x{00C0}-\x{00D6}]
41             | [\x{00D8}-\x{00F6}]
42             | [\x{00F8}-\x{02FF}]
43             | [\x{0370}-\x{037D}]
44             | [\x{037F}-\x{1FFF}]
45             | [\x{200C}-\x{200D}]
46             | [\x{2070}-\x{218F}]
47             | [\x{2C00}-\x{2FEF}]
48             | [\x{3001}-\x{D7FF}]
49             | [\x{F900}-\x{FDCF}]
50             | [\x{FDF0}-\x{FFFD}]
51             | [\x{10000}-\x{EFFFF}]
52             )>x;
53              
54             my $PN_CHARS_U = qr<(?:
55             $PN_CHARS_BASE
56             | [_]
57             )>x;
58              
59             my $PN_CHARS = qr<(?:
60             $PN_CHARS_U
61             | [0-9-]
62             | [\x{00B7}]
63             | [\x{0300}-\x{036F}]
64             | [\x{203F}-\x{2040}]
65             )>x;
66              
67             my $PN_PREFIX = qr<
68             $PN_CHARS_BASE
69             (?:
70             (?: $PN_CHARS | [.] )*
71             $PN_CHARS
72             )?
73             >x;
74              
75             my $PN_LOCAL = qr<
76             (?: $PN_CHARS_U ) # change from Turtle: disallow digits here
77             (?:
78             (?: $PN_CHARS | [.] )*
79             $PN_CHARS
80             )?
81             >x;
82              
83             sub new
84             {
85 14     14 1 6606 my ($class, $suggested, $options) = @_;
86 14   100     80 $suggested ||= {};
87 14   50     65 $options ||= {};
88 14         42 my $self = [{}, {}, $options];
89            
90 14         68 foreach my $s (reverse sort keys %$suggested)
91             {
92 1 50       217 if ($s =~ m< ^ $PN_PREFIX $ >ix)
93             {
94 0         0 $self->[IDX_SUGGESTED]{ $suggested->{$s} } = $s;
95             }
96             else
97             {
98 1         35 carp "Ignored suggestion $s => " . $suggested->{$s};
99             }
100             }
101            
102 14         863 bless $self, $class;
103             }
104              
105             sub get_prefix
106             {
107 15     15 1 26 my ($self, $url) = @_;
108 15         50 my $pp = $self->_practical_prefix($url);
109 15         36 $self->{ $pp } = $url;
110 15         63 return $pp;
111             }
112              
113             sub preview_prefix
114             {
115 1     1 1 6 shift->_practical_prefix(@_);
116             }
117              
118             sub _valid_qname
119             {
120 11     11   21 my ($self, $p, $l) = @_;
121 11 50 33     66 return undef unless defined $p && defined $l;
122 11 50       698 return undef unless $l =~ m< ^ $PN_LOCAL $ >x;
123            
124 11         86 join q(:) => ($p, $l);
125             }
126              
127             sub get_qname
128             {
129 13     13 1 22 my ($self, $url) = @_;
130            
131 13         36 my ($p, $s) = $self->_split_qname($url);
132 13 100 66     124 return undef unless defined $p and defined $s;
133            
134 11         33 return $self->_valid_qname($self->get_prefix($p), $s);
135             }
136              
137             sub preview_qname
138             {
139 0     0 1 0 my ($self, $url) = @_;
140            
141 0         0 my ($p, $s) = $self->_split_qname($url);
142 0 0 0     0 return undef unless defined $p and defined $s;
143            
144 0         0 return $self->_valid_qname($self->preview_prefix($p), $s);
145             }
146              
147             sub get_curie
148             {
149 1     1 1 3 my ($self, $url) = @_;
150            
151 1         4 my ($p, $s) = $self->_split_qname($url);
152            
153 1 50 33     9 return $self->get_prefix($url) . ':'
154             unless defined $p and defined $s;
155            
156 0         0 return $self->get_prefix($p) . ':' . $s;
157             }
158              
159             sub preview_curie
160             {
161 0     0 1 0 my ($self, $url) = @_;
162            
163 0         0 my ($p, $s) = $self->_split_qname($url);
164            
165 0 0 0     0 return $self->preview_prefix($url) . ':'
166             unless defined $p and defined $s;
167            
168 0         0 return $self->preview_prefix($p) . ':' . $s;
169             }
170              
171             sub to_hashref
172             {
173 40     40 1 595 my ($self) = @_;
174 40   50     102 $self->[IDX_USED] ||= {};
175 40         191 return $self->[IDX_USED];
176             }
177              
178             *TO_JSON = \&to_hashref;
179              
180             sub rdfa
181             {
182 0     0 1 0 my ($self) = @_;
183 0         0 my $rv;
184 0         0 foreach my $prefix (sort keys %$self)
185             {
186 0         0 $rv .= sprintf("%s: %s ",
187             $prefix,
188             $self->{$prefix});
189             }
190 0         0 return substr($rv, 0, (length $rv) - 1);
191             }
192              
193             sub sparql
194             {
195 0     0 1 0 my ($self) = @_;
196 0         0 my $rv;
197 0         0 foreach my $prefix (sort keys %$self)
198             {
199 0         0 $rv .= sprintf("PREFIX %s: <%s>\n",
200             $prefix,
201             $self->{$prefix});
202             }
203 0         0 return $rv;
204             }
205              
206             sub turtle
207             {
208 1     1 1 3 my ($self) = @_;
209 1         3 my $rv;
210 1         3 foreach my $prefix (sort keys %$self)
211             {
212 4         11 $rv .= sprintf("\@prefix %-6s <%s> .\n",
213             $prefix.':',
214             $self->{$prefix});
215             }
216 1         9 return $rv;
217             }
218              
219             sub xmlns
220             {
221 0     0 1 0 my ($self) = @_;
222 0         0 my $rv;
223 0         0 foreach my $prefix (sort keys %$self)
224             {
225 0         0 $rv .= sprintf(" xmlns:%s=\"%s\"",
226             $prefix,
227             $self->{$prefix});
228             }
229 0         0 return $rv;
230             }
231              
232             sub to_string
233             {
234 1     1 1 4 my ($self) = @_;
235 1 50       14 if (lc $self->[IDX_OPTIONS]{syntax} eq 'rdfa')
    50          
    50          
236             {
237 0         0 return $self->rdfa;
238             }
239             elsif (lc $self->[IDX_OPTIONS]{syntax} eq 'sparql')
240             {
241 0         0 return $self->sparql;
242             }
243             elsif (lc $self->[IDX_OPTIONS]{syntax} eq 'xmlns')
244             {
245 0         0 return $self->xmlns;
246             }
247             else
248             {
249 1         4 return $self->turtle;
250             }
251             }
252              
253             sub _split_qname
254             {
255 14     14   23 my ($self, $uri) = @_;
256            
257 14 100       3290 if ($uri =~ m< ($PN_LOCAL) $ >x)
258             {
259 11         33 my $ln = $1;
260 11         62 my $ns = substr($uri, 0, length($uri)-length($ln));
261 11         49 return ($ns, $ln);
262             }
263            
264 3         21 return;
265             }
266              
267             my $looks_like_version = qr< ^ [0-9\.-]+ $ >x;
268             my $too_generic = qr< ^(?: terms|ns|vocab|vocabulary|rdf|rdfs|owl|schema|xsd )$ >x;
269              
270             sub _perfect_prefix
271             {
272 15     15   27 my ($self, $url) = @_;
273            
274 15         182 my $chosen = {
275             'http://www.w3.org/1999/02/22-rdf-syntax-ns#' => 'rdf',
276             'http://www.w3.org/2000/01/rdf-schema#' => 'rdfs',
277             'http://www.w3.org/2002/07/owl#' => 'owl',
278             'http://www.w3.org/2001/XMLSchema#' => 'xsd',
279             'http://schema.org/' => 'schema',
280             }->{$url};
281            
282 15 50       82 return $chosen if length $chosen;
283              
284 15     1   842 my @words = map { lc; } ($url =~ m< ((?:$PN_CHARS|\.)+) >xg);
  50         380  
  1         9  
  1         1  
  1         19  
285 15         30299 WORD: while (defined(my $w = pop @words))
286             {
287             next WORD if (
288 20 100 66     965 length $w < 1
      100        
      66        
289             or $w =~ $looks_like_version
290             or $w =~ $too_generic
291             or $w !~ m< ^ $PN_PREFIX $ >x
292             );
293            
294 15         42 $chosen = $w;
295 15         40 last WORD;
296             }
297            
298 15         64 $chosen =~ s< [.] (owl|rdf|rdfx|rdfs|nt|ttl|turtle|xml|org|com|net) $ >()x;
299 15 50       40 $chosen = 'ex' if $chosen eq 'example';
300 15 50       37 return undef unless length $chosen;
301 15         103 return lc $chosen;
302             }
303              
304             sub _practical_prefix
305             {
306 16     16   23 my ($self, $url) = @_;
307            
308 16         22 my %existing = %{ $self->[IDX_USED] };
  16         108  
309 16         64 while (my ($existing_prefix, $full) = each %existing)
310             {
311 8 100       87 return $existing_prefix if $full eq $url;
312             }
313            
314 15   33     74 my $perfect = $self->[IDX_SUGGESTED]{$url}
      50        
315             // $self->_perfect_prefix($url)
316             // 'ns';
317 15 100       46 return $perfect unless $self->_already($perfect);
318            
319 2         4 my $i = 2;
320 2         7 while ($self->_already($perfect.$i))
321             {
322 0         0 $i++;
323             }
324 2         8 return $perfect.$i;
325             }
326              
327             sub _already
328             {
329 17     17   33 my ($self, $prefix) = @_;
330 17         87 return grep { uc $prefix eq uc $_ } keys %$self;
  10         42  
331             }
332              
333             1;
334              
335             __END__
336              
337             =head1 NAME
338              
339             RDF::Prefixes - simple way to turn URIs into QNames
340              
341             =head1 SYNOPSIS
342              
343             my $context = RDF::Prefixes->new;
344             say $context->qname('http://purl.org/dc/terms/title'); # dc:title
345             say $context->qname('http://example.net/rdf/dc#title'); # dc2:title
346             say $context->turtle; # @prefix dc: <http://purl.org/dc/terms/> .
347             # @prefix dc2: <http://example.net/rdf/dc#> .
348              
349             =head1 DESCRIPTION
350              
351             This module is not so much for managing namespaces/prefixes in code (see
352             L<RDF::Trine::NamespaceMap> for that), but as a helper for code that
353             serialises data using namespaces.
354              
355             It generates pretty prefixes, reducing "http://purl.org/dc/terms/"
356             to "dc" rather than something too generic like like "ns01", and provides
357             a context for keeping track of namespaces already used, so that when
358             "http://purl.org/dc/elements/1.1/" is encountered, it won't stomp on
359             the previous definition of "dc".
360              
361             =head2 Constructor
362              
363             =over 4
364              
365             =item C<< new(\%suggestions, \%options) >>
366              
367             Creates a new RDF prefix context.
368              
369             Suggestions for prefix mappings may be given, but there's no guarantee
370             that they'll be used.
371              
372             The only option right now is 'syntax' that is used by the to_string
373             method.
374              
375             Both hashrefs are optional.
376              
377             =back
378              
379             =head2 Methods
380              
381             =over 4
382              
383             =item C<< get_prefix($uri) >>
384              
385             Gets the prefix associated with a URI. e.g.
386             C<< get_prefix('http://purl.org/dc/terms/') >> might return 'dc'.
387              
388             =item C<< get_qname($uri) >>
389              
390             Gets a QName for a URI. e.g.
391             C<< get_qname('http://purl.org/dc/terms/title') >> might return 'dc:title'.
392              
393             Some URIs cannot be converted to QNames. In these cases, undef is returned.
394              
395             =item C<< get_curie($uri) >>
396              
397             As per C<get_qname>, but allows for more relaxed return values, suitable
398             for RDFa, Turtle or Notation 3, but not RDF/XML. Should never need to
399             return undef.
400              
401             =item C<< preview_prefix($uri) >>,
402             C<< preview_qname($uri) >>,
403             C<< preview_curie($uri) >>
404              
405             As per the "get" versions of these methods, but doesn't modify the
406             context.
407              
408             =item C<< to_hashref >>
409              
410             Returns a hashref of prefix mappings used so far. This is not especially
411             necessary as the object may be treated as a hashref directly:
412              
413             foreach my $prefix (keys %$context)
414             {
415             printf("%s => %s\n", $prefix, $context->{$prefix});
416             }
417              
418             =item C<< TO_JSON >>
419              
420             A synonym for to_hashref, provided for the benefit of the L<JSON> package.
421              
422             =item C<< rdfa >>
423              
424             Return the same data as C<to_hashref>, but as a string suitable for
425             placing in an RDFa 1.1 prefix attribute.
426              
427             =item C<< sparql >>
428              
429             Return the same data as C<to_hashref>, but as a string suitable for
430             prefixing a SPARQL query.
431              
432             =item C<< turtle >>
433              
434             Return the same data as C<to_hashref>, but as a string suitable for
435             prefixing a Turtle or Notation 3 file.
436              
437             =item C<< xmlns >>
438              
439             Return the same data as C<to_hashref>, but as a string of xmlns
440             attributes, suitable for use with RDF/XML or RDFa.
441              
442             =item C<< to_string >>
443              
444             Calls either C<rdfa>, C<sparql>, C<turtle> (the default) or C<xmlns>, based on
445             the 'syntax' option passed to the constructor. This module overloads
446             the stringification operator, so explicitly calling to_string is rarely
447             necessary.
448              
449             my $context = RDF::Prefixes->new({}, {syntax=>'turtle'});
450             my $dc_title = 'http://purl.org/dc/terms/title';
451             print "# Prefixes\n" . $context;
452              
453             =back
454              
455             =head2 Internationalisation
456              
457             Strings passed to and from this module are expected to be utf8 character
458             strings, not byte strings. This is not explicitly checked for, but will
459             be checked in a future version, so be warned!
460              
461             URIs containing non-Latin characters should "just work".
462              
463             =head1 BUGS
464              
465             Please report any bugs to L<http://rt.cpan.org/>.
466              
467             =head1 AUTHOR
468              
469             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
470              
471             =head1 COPYRIGHT
472              
473             Copyright 2010-2013 Toby Inkster
474              
475             This library is free software; you can redistribute it and/or modify it
476             under the same terms as Perl itself.
477              
478             =head1 DISCLAIMER OF WARRANTIES
479              
480             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
481             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
482             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.