File Coverage

blib/lib/RDF/NS.pm
Criterion Covered Total %
statement 125 148 84.4
branch 57 88 64.7
condition 14 20 70.0
subroutine 35 36 97.2
pod 14 20 70.0
total 245 312 78.5


line stmt bran cond sub pod time code
1             package RDF::NS;
2 6     6   188252 use v5.10;
  6         43  
3 6     6   29 use strict;
  6         11  
  6         108  
4 6     6   24 use warnings;
  6         11  
  6         243  
5              
6             our $VERSION = '20190227';
7              
8 6     6   33 use Scalar::Util qw(blessed reftype);
  6         10  
  6         328  
9 6     6   2973 use File::ShareDir;
  6         141657  
  6         369  
10 6     6   47 use Carp;
  6         11  
  6         294  
11 6     6   2237 use RDF::SN;
  6         14  
  6         12530  
12              
13             our $AUTOLOAD;
14             our $FORMATS = qr/ttl|n(otation)?3|sparql|xmlns|txt|beacon|json/;
15              
16             our $DATE_REGEXP = qr/^([0-9]{4})-?([0-9][0-9])-?([0-9][0-9])$/;
17              
18             sub new {
19 28 50   28 1 532 my $class = ref( $_[0] ) ? ref(shift) : shift;
20 28 100       118 my $from = @_ % 2 ? shift : 1;
21 28         65 my %options = @_;
22 28   100     151 my $at = $options{at} || 'any';
23 28         52 my $warn = $options{'warn'};
24 28 50       75 $from = $options{from} if exists $options{from};
25 28 100 66     145 $from = 'any' if !$from or $from eq 1;
26              
27 28 100 100     130 if ( ( ref($from) || '' ) eq 'HASH' ) {
28 1         3 my $self = bless $from, $class;
29 1         5 foreach my $prefix ( keys %$self ) {
30 2 100       9 unless ( $self->SET( $prefix => $self->{$prefix}, $warn ) ) {
31 1         3 delete $self->{$prefix};
32             }
33             }
34 1         4 return $self;
35             }
36              
37 27 100       259 if ( $from =~ $DATE_REGEXP ) {
    100          
    50          
38 16         83 $at = "$1$2$3";
39 16         36 $from = 'any';
40             }
41             elsif ( $at =~ $DATE_REGEXP ) {
42 1         6 $at = "$1$2$3";
43             }
44             elsif ( $at !~ 'any' ) {
45 0         0 croak "RDF::NS expects 'any', '1' or a date as YYYY-MM-DD";
46             }
47              
48 27         65 my $self = bless {}, $class;
49 27         87 my $fh = $self->DATA($from);
50 27         21248 foreach (<$fh>) {
51 38090         41825 chomp;
52 38090 50       56214 next if /^#/;
53 38090         88152 my ( $prefix, $namespace, $date ) = split "\t", $_;
54 38090 100 66     113787 last if $date and $at ne 'any' and $date > $at;
      100        
55              
56 38073         54156 $self->SET( $prefix => $namespace, $warn );
57             }
58 27         6319 close($fh);
59              
60 27         416 $self;
61             }
62              
63             sub DATA { # TODO: document
64 27     27 0 69 my ( $self, $from ) = @_;
65 27 50 50     200 $from = File::ShareDir::dist_file( 'RDF-NS', "prefix.cc" )
66             if ( $from // 'any' ) eq 'any';
67 27 50       5680 croak "prefix file or date not found: $from"
68             unless -f $from;
69 27 50       1125 open( my $fh, '<', $from ) or croak "failed to open $from";
70 27         107 $fh;
71             }
72              
73             sub SET {
74 38075     38075 1 54008 my ( $self, $prefix, $namespace, $warn ) = @_;
75              
76 38075 50       92559 if ( $prefix =~ /^(isa|can|new|uri)$/ ) {
    100          
    50          
77 0 0       0 carp "Cannot support prefix '$prefix'" if $warn;
78             }
79             elsif ( $prefix =~ /^[a-z][a-z0-9]*$/ ) {
80 38074 50       83337 if ( $namespace =~ /^[a-z][a-z0-9]*:[^"<>]*$/ ) {
    0          
81 38074         69796 $self->{$prefix} = $namespace;
82 38074         59851 return 1;
83             }
84             elsif ($warn) {
85 0         0 carp "Skipping invalid $prefix namespace $namespace";
86             }
87             }
88             elsif ($warn) {
89 0         0 carp "Skipping unusual prefix '$prefix'";
90             }
91              
92 1         4 return;
93             }
94              
95             *LOAD = *new;
96              
97             sub COUNT {
98 9     9 0 53 scalar keys %{ $_[0] };
  9         90  
99             }
100              
101             sub FORMAT {
102 13     13 0 2069 my $self = shift;
103 13   100     54 my $format = shift || "";
104 13 50       35 $format = 'TTL' if $format =~ /^n(otation)?3$/i;
105 13 100       91 if ( lc($format) =~ $FORMATS ) {
    50          
106 1         6 $format = uc($format);
107 1         7 $self->$format(@_);
108             }
109             elsif ( $format eq "" ) {
110 12     12   101 $self->MAP( sub { $self->{$_} }, @_ );
  12         590  
111             }
112             }
113              
114             sub PREFIX {
115 3     3 1 11 my ( $self, $uri ) = @_;
116 3         837 foreach my $prefix ( sort keys %$self ) {
117 1308 100       1705 return $prefix if $uri eq $self->{$prefix};
118             }
119 1         22 return;
120             }
121              
122             sub PREFIXES {
123 1     1 1 4 my ( $self, $uri ) = @_;
124 1         2 my @prefixes;
125 1         5 while ( my ( $prefix, $namespace ) = each %$self ) {
126 698 100       1338 push @prefixes, $prefix if $uri eq $namespace;
127             }
128 1         10 return ( sort(@prefixes) );
129             }
130              
131             sub REVERSE {
132 21     21 1 4698 RDF::SN->new( $_[0] );
133             }
134              
135             sub TTL {
136 5     5 1 1642 my $self = shift;
137 5     9   28 $self->MAP( sub { "\@prefix $_: <" . $self->{$_} . "> ." }, @_ );
  9         44  
138             }
139              
140             sub SPARQL {
141 5     5 1 2095 my $self = shift;
142 5     9   31 $self->MAP( sub { "PREFIX $_: <" . $self->{$_} . ">" }, @_ );
  9         39  
143             }
144              
145             sub XMLNS {
146 6     6 1 2107 my $self = shift;
147 6     10   35 $self->MAP( sub { "xmlns:$_=\"" . $self->{$_} . "\"" }, @_ );
  10         46  
148             }
149              
150             sub TXT {
151 4     4 1 2057 my $self = shift;
152 4     8   24 $self->MAP( sub { "$_\t" . $self->{$_} }, @_ );
  8         32  
153             }
154              
155             sub JSON {
156 1     1 0 2 my $self = shift;
157 1     2   9 $self->MAP( sub { "\"$_\": \"" . $self->{$_} . "\"" }, @_ );
  2         235  
158             }
159              
160             sub BEACON {
161 4     4 1 2092 my $self = shift;
162 4     8   22 $self->MAP( sub { "#PREFIX: " . $self->{$_} }, @_ );
  8         28  
163             }
164              
165             sub SELECT {
166 2     2 1 1025 my $self = shift;
167 2     3   11 $self->MAP( sub { $_ => $self->{$_} }, @_ );
  3         13  
168             }
169              
170             # functional programming rulez!
171             sub MAP {
172 39     39 1 73 my $self = shift;
173 39         55 my $code = shift;
174             my @ns =
175             @_
176 39 50       116 ? ( grep { $self->{$_} } map { split /[|, ]+/ } @_ )
  75         196  
  52         249  
177             : keys %$self;
178 39 100       110 if (wantarray) {
179 34         1129 return map { $code->() } sort @ns;
  56         95  
180             }
181             else {
182 5         9 local $_ = $ns[0];
183 5         18 return $code->();
184             }
185             }
186              
187             sub GET {
188 21     21 1 114 $_[1];
189             }
190              
191       5 0   sub BLANK {
192             }
193              
194             *URI = *uri;
195              
196             sub uri {
197 8     8 1 1417 my $self = shift;
198 8 100       38 return $1 if $_[0] =~ /^<([a-zA-Z][a-zA-Z+.-]*:.+)>$/;
199 7 100       34 return $self->BLANK( $_[0] ) if $_[0] =~ /^_(:.*)?$/;
200 4 50       31 return unless shift =~ /^([a-z][a-z0-9]*)?([:_]([^:]+))?$/;
201 4 100       23 my $ns = $self->{ defined $1 ? $1 : '' };
202 4 50       16 return unless defined $ns;
203 4 50       18 return $self->GET($ns) unless $3;
204 4         17 return $self->GET( $ns . $3 );
205             }
206              
207             sub AUTOLOAD {
208 52     52   22223 my $self = shift;
209 52 100       8284 return unless $AUTOLOAD =~ /^.*::([a-z][a-z0-9]*)?(_([^:]+)?)?$/;
210 23 100       99 return $self->BLANK( defined $3 ? "_:$3" : '_' ) unless $1;
    100          
211 21 50       74 my $ns = $self->{$1} or return;
212 21 100       69 my $local = defined $3 ? $3 : shift;
213 21 100       71 return $self->GET($ns) unless defined $local;
214 9         35 return $self->GET( $ns . $local );
215             }
216              
217             sub UPDATE {
218 0     0 0   my ( $self, $file, $date ) = @_;
219              
220 0 0 0       croak "RDF::NS expects a date as YYYY-MM-DD"
221             unless $date and $date =~ $DATE_REGEXP;
222 0           $date = "$1$2$3";
223              
224 0           my $old = RDF::NS->new($file);
225 0           my ( @create, @update, @delete );
226              
227 0 0         open( my $fh, '>>', $file ) or croak "failed to open $file";
228 0           my @lines;
229              
230 0           while ( my ( $prefix, $namespace ) = each %$self ) {
231 0 0         if ( !exists $old->{$prefix} ) {
    0          
232 0           push @create, $prefix;
233             }
234             elsif ( $old->{$prefix} ne $namespace ) {
235 0           push @update, $prefix;
236             }
237             else {
238 0           next;
239             }
240 0           push @lines, "$prefix\t$namespace";
241             }
242 0           while ( my ( $prefix, $namespace ) = each %$old ) {
243 0 0         if ( !exists $self->{$prefix} ) {
244 0           push @delete, $prefix;
245             }
246             }
247              
248 0           print $fh "$_\t$date\n" for sort @lines;
249 0           close $fh;
250              
251             return {
252 0           create => [ sort @create ],
253             update => [ sort @update ],
254             delete => [ sort @delete ],
255             };
256             }
257              
258             1;
259             __END__