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.10
10              
11             =cut
12              
13              
14             package URI::NamespaceMap;
15 6     6   43926 use Moo 1.006000;
  6         19438  
  6         38  
16 6     6   6057 use Module::Load::Conditional qw[can_load];
  6         116932  
  6         336  
17 6     6   959 use URI::Namespace;
  6         14  
  6         144  
18 6     6   38 use Carp;
  6         24  
  6         277  
19 6     6   44 use Scalar::Util qw( blessed );
  6         11  
  6         262  
20 6     6   31 use Sub::Quote qw( quote_sub );
  6         9  
  6         209  
21 6     6   28 use Try::Tiny;
  6         10  
  6         264  
22 6     6   27 use Types::Standard qw(HashRef);
  6         14  
  6         71  
23 6     6   4545 use Types::Namespace 0.004 qw(Namespace);
  6         111  
  6         33  
24 6     6   4519 use URI::NamespaceMap::ReservedLocalParts;
  6         15  
  6         208  
25 6     6   865 use namespace::autoclean;
  6         17076  
  6         43  
26              
27              
28             our $VERSION = '1.10';
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 6283 my ($self, $args) = @_;
125 16         249 my $r = URI::NamespaceMap::ReservedLocalParts->new(disallowed => [qw/uri/]);
126 16         903 for my $local_part (keys %{$args->{namespace_map}}) {
  16         68  
127 25 100       61 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 846 my $r = URI::NamespaceMap::ReservedLocalParts->new(disallowed => [qw/uri/]);
141 7 100       429 Carp::croak("$_[1] prohibited as local part") if $r->is_reserved($_[1]);
142              
143 6         32 $_[0]->namespace_map->{$_[1]} = Namespace->assert_coerce($_[2])
144             }
145 1     1 1 323 sub remove_mapping { delete $_[0]->namespace_map->{$_[1]} }
146 68     68 1 6977 sub namespace_uri { $_[0]->namespace_map->{$_[1]} }
147 1     1 1 520 sub list_namespaces { values %{ $_[0]->namespace_map } }
  1         6  
148 6     6 1 4287 sub list_prefixes { keys %{ $_[0]->namespace_map } }
  6         46  
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 1622 my ($self, @data) = @_;
162 4         12 my $guessed = $self->_guess(@data);
163 4         9 while (my ($name, $uri) = each %{$guessed}) {
  8         1858  
164 4         15 $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 2028 my $self = shift;
176 4         5 my $abbr = shift;
177 4         4 my $ns;
178 4         5 my $local = "";
179 4 50       46 if ($abbr =~ m/^([^:]*):(.*)$/) {
180 4         9 $ns = $self->namespace_uri( $1 );
181 4         9 $local = $2;
182             } else {
183 0         0 $ns = $self->{ $abbr };
184             }
185 4 50       25 return unless (blessed($ns));
186 4 100       11 if ($local ne '') {
187 3         9 return $ns->uri($local);
188             } else {
189 1         46 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   9 my $uri = shift;
203 8 100       16 if (ref $uri) {
204 4 50       12 if (blessed $uri) {
205 4 100       23 if ($uri->isa('URI::Namespace')) {
    50          
    50          
    0          
    0          
206 2         27 $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         5 $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         153 return $uri;
238             }
239              
240             sub prefix_for {
241 3     3 1 6 my ($self, $uri) = @_;
242              
243 3         4 $uri = _scrub_uri($uri);
244              
245 3         3 my @candidates;
246 3         6 for my $k ($self->list_prefixes) {
247 9         18 my $v = $self->namespace_uri($k);
248              
249 9         126 my $nsuri = $v->as_string;
250              
251             # the input should always be longer than the namespace
252 9 100       630 next if length $nsuri > length $uri;
253              
254             # candidate namespace must match exactly
255 4         9 my $cns = substr($uri, 0, length $nsuri);
256 4 100       11 push @candidates, $k if $cns eq $nsuri;
257             }
258              
259             # make sure this behaves correctly when empty
260 3 100       8 return unless @candidates;
261              
262             # if this returns more than one prefix, take the
263             # shortest/lexically lowest one.
264 2         5 @candidates = sort @candidates;
265              
266 2 50       6 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 119 my ($self, $uri) = @_;
282              
283 3         8 $uri = _scrub_uri($uri);
284              
285 3         7 my $prefix = $self->prefix_for($uri);
286              
287 3 100       9 return unless defined $prefix;
288              
289 2         3 my $nsuri = _scrub_uri($self->namespace_uri($prefix));
290              
291 2         23 return sprintf('%s:%s', $prefix, substr($uri, length $nsuri));
292             }
293              
294             our $AUTOLOAD;
295             sub AUTOLOAD {
296 22     22   9959 my ($self, $arg) = @_;
297 22         168 my ($name) = ($AUTOLOAD =~ /::(\w+)$/);
298 22         52 my $ns = $self->namespace_uri($name);
299 22 100       154 return unless $ns;
300 5 100       25 return $ns->$arg if $arg;
301 3         30 return $ns;
302             }
303              
304             sub _guess {
305 11     11   35 my ($self, @data) = @_;
306 11         61 my $rnscu = can_load( modules => { 'RDF::NS::Curated' => 0 } );
307 11         5694 my $xmlns = can_load( modules => { 'XML::CommonNS' => 0 } );
308 11         5374 my $rdfns = can_load( modules => { 'RDF::NS' => 20130802 } );
309 11         6150 my $rdfpr = can_load( modules => { 'RDF::Prefixes' => 0 } );
310              
311 11 0 33     9439 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         18 my %namespaces;
313 11         219 my $r = URI::NamespaceMap::ReservedLocalParts->new(disallowed => [qw/uri/]);
314              
315 11         663 foreach my $entry (@data) {
316 17 100       78 if ($entry =~ m/^[a-z]\w+$/i) {
317             # This is a prefix
318 8 0 33     21 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         11 my $prefix = $entry;
321 8         24 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       18 if ($rnscu) {
328 8         35 my $ns = RDF::NS::Curated->new;
329 8         282 $namespaces{$prefix} = $ns->uri($entry);
330             }
331 8 50 33     103 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     20 if ((! $namespaces{$prefix}) && $rdfns) {
339 0         0 my $ns = RDF::NS->new;
340 0         0 $namespaces{$prefix} = $ns->SELECT($entry);
341             }
342 8 50       21 carp "Cannot resolve assumed prefix '$entry'" unless $namespaces{$prefix};
343             } else {
344             # Lets assume a URI string
345 9 0 33     36 carp "Cannot resolve '$entry' without RDF::NS::Curated, RDF::NS or RDF::Prefixes" unless ($rnscu || $rdfns || $rdfpr);
      33        
346 9         14 my $prefix;
347 9 50       19 if ($rnscu) {
348 9         42 my $ns = RDF::NS::Curated->new;
349 9         309 $prefix = $ns->prefix($entry);
350             }
351 9 100 66     527 if ((! $prefix) && ($rdfns)) {
352 4         27 my $ns = RDF::NS->new;
353 4         125068 $prefix = $ns->PREFIX($entry);
354             }
355 9 100 66     14519 if ((! $prefix) && ($rdfpr)) {
356 3         36 my $context = RDF::Prefixes->new;
357 3         69 $prefix = $context->get_prefix($entry);
358             }
359 9 50       714 unless ($prefix) {
360 0         0 carp "Cannot resolve assumed URI string '$entry'";
361             } else {
362 9         15 my $i = 1;
363 9         38 while ($r->is_reserved($prefix)) {
364 2         6 $prefix .= 'x';
365 2 50       6 carp "Cannot resolve '$entry' as tried prefix '$prefix' conflicts with method names." if ($i > 5);
366 2         6 $i++;
367             }
368 9         31 $namespaces{$prefix} = $entry;
369             }
370             }
371             }
372 11         209 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__