File Coverage

blib/lib/URI/NamespaceMap.pm
Criterion Covered Total %
statement 133 151 88.0
branch 41 66 62.1
condition 12 33 36.3
subroutine 24 26 92.3
pod 10 11 90.9
total 220 287 76.6


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             URI::NamespaceMap - Class holding a collection of namespaces
6              
7             =head1 VERSION
8              
9             Version 1.09_02
10              
11             =cut
12              
13              
14             package URI::NamespaceMap;
15 6     6   54352 use Moo 1.006000;
  6         23235  
  6         43  
16 6     6   7088 use Module::Load::Conditional qw[can_load];
  6         139363  
  6         409  
17 6     6   1130 use URI::Namespace;
  6         17  
  6         172  
18 6     6   51 use Carp;
  6         23  
  6         344  
19 6     6   55 use Scalar::Util qw( blessed );
  6         14  
  6         318  
20 6     6   38 use Sub::Quote qw( quote_sub );
  6         12  
  6         275  
21 6     6   40 use Try::Tiny;
  6         12  
  6         320  
22 6     6   36 use Types::Standard qw(HashRef);
  6         14  
  6         85  
23 6     6   5618 use Types::Namespace 0.004 qw(Namespace);
  6         137  
  6         45  
24 6     6   5080 use URI::NamespaceMap::ReservedLocalParts;
  6         20  
  6         217  
25 6     6   984 use namespace::autoclean;
  6         20297  
  6         49  
26              
27              
28             our $VERSION = '1.09_02';
29              
30              
31             =head1 SYNOPSIS
32              
33             use URI::NamespaceMap;
34             my $map = URI::NamespaceMap->new( { xsd => 'http://www.w3.org/2001/XMLSchema#' } );
35             $map->namespace_uri('xsd')->as_string;
36             my $foaf = URI::Namespace->new( 'http://xmlns.com/foaf/0.1/' );
37             $map->add_mapping(foaf => $foaf);
38             $map->add_mapping(rdf => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' );
39             $map->list_prefixes; # ( 'foaf', 'rdf', 'xsd' )
40             $map->foaf; # Returns URI::Namespace object
41             while (my ($prefix, $nsURI) = $map->each_map) {
42             $node->setNamespace($nsURI->as_string, $prefix); # For use with XML::LibXML
43             }
44              
45              
46             =head1 DESCRIPTION
47              
48             This module provides an object to manage multiple namespaces for creating L<URI::Namespace> objects and for serializing.
49              
50             =head1 METHODS
51              
52             =over
53              
54             =item C<< new ( [ \%namespaces | @prefixes | @uris ] ) >>
55              
56             Returns a new namespace map object. You can pass a hash reference with
57             mappings from local names to namespace URIs (given as string or
58             L<RDF::Trine::Node::Resource>) or namespaces_map with a hashref.
59              
60             You may also pass an arrayref containing just prefixes and/or
61             namespace URIs, and the module will try to guess the missing part. To
62             use this feature, you need L<RDF::NS::Curated>, L<RDF::NS>,
63             L<XML::CommonNS> or L<RDF::Prefixes>, or preferably all of them. With
64             that, you can do e.g.
65              
66             my $map = URI::NamespaceMap->new( 'rdf', 'xsd', 'foaf' );
67              
68             and have the correct mappings added automatically.
69              
70              
71              
72             =item C<< add_mapping ( $name => $uri ) >>
73              
74             Adds a new namespace to the map. The namespace URI can be passed
75             as string or a L<URI::Namespace> object.
76              
77             =item C<< remove_mapping ( $name ) >>
78              
79             Removes a namespace from the map given a prefix.
80              
81             =item C<< namespace_uri ( $name ) >>
82              
83             Returns the L<URI::Namespace> object (if any) associated with the given prefix.
84              
85             =item C<< $name >>
86              
87             This module creates a method for all the prefixes, so you can say e.g.
88              
89             $map->foaf
90              
91             and get a L<URI::Namespace> object for the FOAF namespace. Since
92             L<URI::Namespace> does the same for local names, you can then say e.g.
93              
94             $map->foaf->name
95              
96             to get a full L<URI>.
97              
98             =item C<< list_namespaces >>
99              
100             Returns an array of L<URI::Namespace> objects with all the namespaces.
101              
102             =item C<< list_prefixes >>
103              
104             Returns an array of prefixes.
105              
106             =item C<< each_map >>
107              
108             Returns an 2-element list where the first element is a prefix and the
109             second is the corresponding L<URI::Namespace> object.
110              
111             =cut
112              
113             around BUILDARGS => sub {
114             my ($next, $self, @parameters) = @_;
115             if (ref($parameters[0]) eq 'ARRAY') {
116             return { namespace_map => $self->_guess(@{$parameters[0]}) };
117             }
118             if (@parameters == 1 && (! exists $parameters[0]->{namespace_map})) {
119             return { namespace_map => $parameters[0] }
120             } else { $self->$next(@parameters) }
121             };
122              
123             sub BUILD {
124 16     16 0 7725 my ($self, $args) = @_;
125 16         303 my $r = URI::NamespaceMap::ReservedLocalParts->new(disallowed => [qw/uri/]);
126 16         1102 for my $local_part (keys %{$args->{namespace_map}}) {
  16         83  
127 25 100       68 Carp::croak("$_[1] prohibited as local part")
128             if $r->is_reserved($local_part);
129             }
130             }
131              
132             has namespace_map => (
133             is => "ro",
134             isa => HashRef[Namespace],
135             coerce => 1,
136             default => quote_sub q { {} },
137             );
138              
139             sub add_mapping {
140 7     7 1 912 my $r = URI::NamespaceMap::ReservedLocalParts->new(disallowed => [qw/uri/]);
141 7 100       549 Carp::croak("$_[1] prohibited as local part") if $r->is_reserved($_[1]);
142              
143 6         36 $_[0]->namespace_map->{$_[1]} = Namespace->assert_coerce($_[2])
144             }
145 1     1 1 397 sub remove_mapping { delete $_[0]->namespace_map->{$_[1]} }
146 68     68 1 8268 sub namespace_uri { $_[0]->namespace_map->{$_[1]} }
147 1     1 1 632 sub list_namespaces { values %{ $_[0]->namespace_map } }
  1         8  
148 6     6 1 5312 sub list_prefixes { keys %{ $_[0]->namespace_map } }
  6         60  
149 0     0 1 0 sub each_map { each %{ $_[0]->namespace_map } }
  0         0  
150              
151              
152             =item C<< guess_and_add ( @string_or_uri ) >>
153              
154             Like in the constructor, an array of strings can be given, and the
155             module will attempt to guess appropriate mappings, and add them to the
156             map.
157              
158             =cut
159              
160             sub guess_and_add {
161 4     4 1 1974 my ($self, @data) = @_;
162 4         11 my $guessed = $self->_guess(@data);
163 4         9 while (my ($name, $uri) = each %{$guessed}) {
  8         1935  
164 4         17 $self->add_mapping($name => $uri);
165             }
166             }
167              
168             =item C<< uri ( $prefixed_name ) >>
169              
170             Returns a URI for an abbreviated string such as 'foaf:Person'.
171              
172             =cut
173              
174             sub uri {
175 4     4 1 2433 my $self = shift;
176 4         9 my $abbr = shift;
177 4         7 my $ns;
178 4         7 my $local = "";
179 4 50       52 if ($abbr =~ m/^([^:]*):(.*)$/) {
180 4         11 $ns = $self->namespace_uri( $1 );
181 4         10 $local = $2;
182             } else {
183 0         0 $ns = $self->{ $abbr };
184             }
185 4 50       28 return unless (blessed($ns));
186 4 100       13 if ($local ne '') {
187 3         10 return $ns->uri($local);
188             } else {
189 1         23 return URI->new($ns->as_string);
190             }
191             }
192              
193             =item prefix_for C<< uri ($uri) >>
194              
195             Returns the associated prefix (or potentially multiple prefixes, when
196             called in list context) for the given URI.
197              
198             =cut
199              
200             # turn the URI back into a string to mitigate unexpected behaviour
201             sub _scrub_uri {
202 8     8   12 my $uri = shift;
203 8 100       19 if (ref $uri) {
204 4 50       17 if (blessed $uri) {
205 4 100       27 if ($uri->isa('URI::Namespace')) {
    50          
    50          
    0          
    0          
206 2         33 $uri = $uri->as_string;
207             }
208             elsif ($uri->isa('IRI')) {
209 0         0 $uri = $uri->as_string;
210             }
211             elsif ($uri->isa('URI')) {
212             # it's probably not necessary to do this, but whatever
213 2         7 $uri = $uri->as_string;
214             }
215             elsif ($uri->isa('RDF::Trine::Node')) {
216             # it is, on the other hand, necessary to do this.
217 0         0 $uri = $uri->uri_value;
218             }
219             elsif ($uri->isa('RDF::Trine::Namespace')) {
220             # and this
221 0         0 $uri = $uri->uri->uri_value;
222             }
223             # elsif ($uri =~ m/^\<(.*?)\>$/) {
224             # $uri = $1;
225             # }
226             else {
227             # let's hope whatever was passed in has a string overload
228 0         0 $uri = "$uri";
229             }
230             }
231             else {
232 0         0 Carp::croak(sprintf "You probably didn't mean to pass this " .
233             "an unblessed %s reference", ref $uri);
234             }
235             }
236              
237 8         186 return $uri;
238             }
239              
240             sub prefix_for {
241 3     3 1 8 my ($self, $uri) = @_;
242              
243 3         6 $uri = _scrub_uri($uri);
244              
245 3         6 my @candidates;
246 3         8 for my $k ($self->list_prefixes) {
247 9         20 my $v = $self->namespace_uri($k);
248              
249 9         155 my $nsuri = $v->as_string;
250              
251             # the input should always be longer than the namespace
252 9 100       758 next if length $nsuri > length $uri;
253              
254             # candidate namespace must match exactly
255 4         11 my $cns = substr($uri, 0, length $nsuri);
256 4 100       14 push @candidates, $k if $cns eq $nsuri;
257             }
258              
259             # make sure this behaves correctly when empty
260 3 100       12 return unless @candidates;
261              
262             # if this returns more than one prefix, take the
263             # shortest/lexically lowest one.
264 2         7 @candidates = sort @candidates;
265              
266 2 50       7 return wantarray ? @candidates : $candidates[0];
267             }
268              
269             =item abbreviate C<< uri ($uri) >>
270              
271             Complement to L</namespace_uri>. Returns the given URI in C<foo:bar>
272             format or C<undef> if it wasn't matched, therefore the idiom
273              
274             my $str = $nsmap->abbreviate($uri_node) || $uri->as_string;
275              
276             may be useful for certain serialization tasks.
277              
278             =cut
279              
280             sub abbreviate {
281 3     3 1 144 my ($self, $uri) = @_;
282              
283 3         9 $uri = _scrub_uri($uri);
284              
285 3         8 my $prefix = $self->prefix_for($uri);
286              
287 3 100       13 return unless defined $prefix;
288              
289 2         4 my $nsuri = _scrub_uri($self->namespace_uri($prefix));
290              
291 2         28 return sprintf('%s:%s', $prefix, substr($uri, length $nsuri));
292             }
293              
294             our $AUTOLOAD;
295             sub AUTOLOAD {
296 22     22   12359 my ($self, $arg) = @_;
297 22         558 my ($name) = ($AUTOLOAD =~ /::(\w+)$/);
298 22         68 my $ns = $self->namespace_uri($name);
299 22 100       188 return unless $ns;
300 5 100       28 return $ns->$arg if $arg;
301 3         39 return $ns;
302             }
303              
304             sub _guess {
305 11     11   34 my ($self, @data) = @_;
306 11         59 my $rnscu = can_load( modules => { 'RDF::NS::Curated' => 0 } );
307 11         7000 my $xmlns = can_load( modules => { 'XML::CommonNS' => 0 } );
308 11         6927 my $rdfns = can_load( modules => { 'RDF::NS' => 20130802 } );
309 11         7553 my $rdfpr = can_load( modules => { 'RDF::Prefixes' => 0 } );
310              
311 11 0 33     11716 confess 'To resolve an array, you need at least one of RDF::NS::Curated, XML::CommonNS, RDF::NS or RDF::Prefixes' unless ($rnscu || $xmlns || $rdfns || $rdfpr);
      33        
      0        
312 11         20 my %namespaces;
313 11         255 my $r = URI::NamespaceMap::ReservedLocalParts->new(disallowed => [qw/uri/]);
314              
315 11         811 foreach my $entry (@data) {
316 17 100       88 if ($entry =~ m/^[a-z]\w+$/i) {
317             # This is a prefix
318 8 0 33     28 carp "Cannot resolve '$entry' without RDF::NS::Curated, XML::CommonNS, RDF::NS" unless ($rnscu || $xmlns || $rdfns);
      33        
319 8         11 my $i = 1;
320 8         13 my $prefix = $entry;
321 8         27 while ($r->is_reserved($prefix)) {
322 0         0 $prefix .= 'x';
323 0 0       0 carp "Cannot resolve '$entry' as tried prefix '$prefix' conflicts with method names." if ($i > 5);
324 0         0 $i++;
325             }
326              
327 8 50       21 if ($rnscu) {
328 8         35 my $ns = RDF::NS::Curated->new;
329 8         345 $namespaces{$prefix} = $ns->uri($entry);
330             }
331 8 50 33     134 if ((! $namespaces{$prefix}) && $xmlns) {
332 0         0 require XML::CommonNS;
333 0         0 XML::CommonNS->import(':all');
334             try {
335 0     0   0 $namespaces{$prefix} = XML::CommonNS->uri(uc($entry))->toString;
336 0         0 }; # Then, XML::CommonNS doesn't have the prefix, which is OK, we just continue
337             }
338 8 50 33     21 if ((! $namespaces{$prefix}) && $rdfns) {
339 0         0 my $ns = RDF::NS->new;
340 0         0 $namespaces{$prefix} = $ns->SELECT($entry);
341             }
342 8 50       25 carp "Cannot resolve assumed prefix '$entry'" unless $namespaces{$prefix};
343             } else {
344             # Lets assume a URI string
345 9 0 33     40 carp "Cannot resolve '$entry' without RDF::NS::Curated, RDF::NS or RDF::Prefixes" unless ($rnscu || $rdfns || $rdfpr);
      33        
346 9         16 my $prefix;
347 9 50       22 if ($rnscu) {
348 9         43 my $ns = RDF::NS::Curated->new;
349 9         388 $prefix = $ns->prefix($entry);
350             }
351 9 100 66     594 if ((! $prefix) && ($rdfns)) {
352 4         33 my $ns = RDF::NS->new;
353 4         151159 $prefix = $ns->PREFIX($entry);
354             }
355 9 100 66     16328 if ((! $prefix) && ($rdfpr)) {
356 3         32 my $context = RDF::Prefixes->new;
357 3         75 $prefix = $context->get_prefix($entry);
358             }
359 9 50       846 unless ($prefix) {
360 0         0 carp "Cannot resolve assumed URI string '$entry'";
361             } else {
362 9         17 my $i = 1;
363 9         39 while ($r->is_reserved($prefix)) {
364 2         7 $prefix .= 'x';
365 2 50       6 carp "Cannot resolve '$entry' as tried prefix '$prefix' conflicts with method names." if ($i > 5);
366 2         7 $i++;
367             }
368 9         37 $namespaces{$prefix} = $entry;
369             }
370             }
371             }
372 11         286 return \%namespaces;
373             }
374              
375             =back
376              
377             =head1 WARNING
378              
379             Avoid using the names 'can', 'isa', 'VERSION', and 'DOES' as namespace
380             prefix, because these names are defined as method for every Perl
381             object by default. The method names 'new' and 'uri' are also
382             forbidden. Names of methods of L<Moose::Object> must also be avoided.
383              
384             Using them will result in an error.
385              
386             =head1 AUTHORS
387              
388             Chris Prather, C<< <chris@prather.org> >>
389             Kjetil Kjernsmo, C<< <kjetilk@cpan.org> >>
390             Gregory Todd Williams, C<< <gwilliams@cpan.org> >>
391             Toby Inkster, C<< <tobyink@cpan.org> >>
392              
393             =head1 CONTRIBUTORS
394              
395             Dorian Taylor
396             Paul Williams
397              
398             =head1 BUGS
399              
400             Please report any bugs using L<github|https://github.com/kjetilk/URI-NamespaceMap/issues>
401              
402              
403             =head1 SUPPORT
404              
405             You can find documentation for this module with the perldoc command.
406              
407             perldoc URI::NamespaceMap
408              
409             =head1 COPYRIGHT & LICENSE
410              
411             Copyright 2012,2013,2014,2015,2016,2017,2018,2019 Gregory Todd Williams, Chris Prather and Kjetil Kjernsmo
412              
413             This program is free software; you can redistribute it and/or modify it
414             under the same terms as Perl itself.
415              
416              
417             =cut
418              
419             1;
420             __END__