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   211099 use v5.10;
  6         53  
3 6     6   31 use strict;
  6         10  
  6         119  
4 6     6   34 use warnings;
  6         15  
  6         257  
5              
6             our $VERSION = '20230619';
7              
8 6     6   35 use Scalar::Util qw(blessed reftype);
  6         9  
  6         336  
9 6     6   3199 use File::ShareDir;
  6         173708  
  6         266  
10 6     6   45 use Carp;
  6         14  
  6         307  
11 6     6   2511 use RDF::SN;
  6         18  
  6         13998  
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 570 my $class = ref( $_[0] ) ? ref(shift) : shift;
20 28 100       103 my $from = @_ % 2 ? shift : 1;
21 28         64 my %options = @_;
22 28   100     127 my $at = $options{at} || 'any';
23 28         51 my $warn = $options{'warn'};
24 28 50       83 $from = $options{from} if exists $options{from};
25 28 100 66     153 $from = 'any' if !$from or $from eq 1;
26              
27 28 100 100     119 if ( ( ref($from) || '' ) eq 'HASH' ) {
28 1         5 my $self = bless $from, $class;
29 1         5 foreach my $prefix ( keys %$self ) {
30 2 100       6 unless ( $self->SET( $prefix => $self->{$prefix}, $warn ) ) {
31 1         8 delete $self->{$prefix};
32             }
33             }
34 1         16 return $self;
35             }
36              
37 27 100       267 if ( $from =~ $DATE_REGEXP ) {
    100          
    50          
38 16         80 $at = "$1$2$3";
39 16         31 $from = 'any';
40             }
41             elsif ( $at =~ $DATE_REGEXP ) {
42 1         5 $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         64 my $self = bless {}, $class;
49 27         76 my $fh = $self->DATA($from);
50 27         33623 foreach (<$fh>) {
51 46930         57796 chomp;
52 46930 50       77047 next if /^#/;
53 46930         117225 my ( $prefix, $namespace, $date ) = split "\t", $_;
54 46930 100 66     149585 last if $date and $at ne 'any' and $date > $at;
      100        
55              
56 46913         71900 $self->SET( $prefix => $namespace, $warn );
57             }
58 27         7015 close($fh);
59              
60 27         394 $self;
61             }
62              
63             sub DATA { # TODO: document
64 27     27 0 70 my ( $self, $from ) = @_;
65 27 50 50     176 $from = File::ShareDir::dist_file( 'RDF-NS', "prefix.cc" )
66             if ( $from // 'any' ) eq 'any';
67 27 50       5698 croak "prefix file or date not found: $from"
68             unless -f $from;
69 27 50       1204 open( my $fh, '<', $from ) or croak "failed to open $from";
70 27         119 $fh;
71             }
72              
73             sub SET {
74 46915     46915 1 74672 my ( $self, $prefix, $namespace, $warn ) = @_;
75              
76 46915 50       124878 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 46914 50       115388 if ( $namespace =~ /^[a-z][a-z0-9]*:[^"<>]*$/ ) {
    0          
81 46914         97125 $self->{$prefix} = $namespace;
82 46914         82524 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         2 return;
93             }
94              
95             *LOAD = *new;
96              
97             sub COUNT {
98 9     9 0 41 scalar keys %{ $_[0] };
  9         58  
99             }
100              
101             sub FORMAT {
102 13     13 0 2186 my $self = shift;
103 13   100     55 my $format = shift || "";
104 13 50       41 $format = 'TTL' if $format =~ /^n(otation)?3$/i;
105 13 100       107 if ( lc($format) =~ $FORMATS ) {
    50          
106 1         9 $format = uc($format);
107 1         10 $self->$format(@_);
108             }
109             elsif ( $format eq "" ) {
110 12     12   123 $self->MAP( sub { $self->{$_} }, @_ );
  12         699  
111             }
112             }
113              
114             sub PREFIX {
115 3     3 1 17 my ( $self, $uri ) = @_;
116 3         1067 foreach my $prefix ( sort keys %$self ) {
117 1308 100       2498 return $prefix if $uri eq $self->{$prefix};
118             }
119 1         32 return;
120             }
121              
122             sub PREFIXES {
123 1     1 1 5 my ( $self, $uri ) = @_;
124 1         2 my @prefixes;
125 1         8 while ( my ( $prefix, $namespace ) = each %$self ) {
126 698 100       1652 push @prefixes, $prefix if $uri eq $namespace;
127             }
128 1         31 return ( sort(@prefixes) );
129             }
130              
131             sub REVERSE {
132 21     21 1 5616 RDF::SN->new( $_[0] );
133             }
134              
135             sub TTL {
136 5     5 1 2050 my $self = shift;
137 5     9   30 $self->MAP( sub { "\@prefix $_: <" . $self->{$_} . "> ." }, @_ );
  9         40  
138             }
139              
140             sub SPARQL {
141 5     5 1 2071 my $self = shift;
142 5     9   35 $self->MAP( sub { "PREFIX $_: <" . $self->{$_} . ">" }, @_ );
  9         42  
143             }
144              
145             sub XMLNS {
146 6     6 1 2046 my $self = shift;
147 6     10   35 $self->MAP( sub { "xmlns:$_=\"" . $self->{$_} . "\"" }, @_ );
  10         57  
148             }
149              
150             sub TXT {
151 4     4 1 1754 my $self = shift;
152 4     8   24 $self->MAP( sub { "$_\t" . $self->{$_} }, @_ );
  8         34  
153             }
154              
155             sub JSON {
156 1     1 0 5 my $self = shift;
157 1     2   11 $self->MAP( sub { "\"$_\": \"" . $self->{$_} . "\"" }, @_ );
  2         472  
158             }
159              
160             sub BEACON {
161 4     4 1 2175 my $self = shift;
162 4     8   24 $self->MAP( sub { "#PREFIX: " . $self->{$_} }, @_ );
  8         31  
163             }
164              
165             sub SELECT {
166 2     2 1 1162 my $self = shift;
167 2     3   13 $self->MAP( sub { $_ => $self->{$_} }, @_ );
  3         25  
168             }
169              
170             # functional programming rulez!
171             sub MAP {
172 39     39 1 79 my $self = shift;
173 39         58 my $code = shift;
174             my @ns =
175             @_
176 39 50       119 ? ( grep { $self->{$_} } map { split /[|, ]+/ } @_ )
  75         208  
  52         268  
177             : keys %$self;
178 39 100       111 if (wantarray) {
179 34         2022 return map { $code->() } sort @ns;
  56         88  
180             }
181             else {
182 5         9 local $_ = $ns[0];
183 5         19 return $code->();
184             }
185             }
186              
187             sub GET {
188 21     21 1 140 $_[1];
189             }
190              
191       5 0   sub BLANK {
192             }
193              
194             *URI = *uri;
195              
196             sub uri {
197 8     8 1 1269 my $self = shift;
198 8 100       38 return $1 if $_[0] =~ /^<([a-zA-Z][a-zA-Z+.-]*:.+)>$/;
199 7 100       37 return $self->BLANK( $_[0] ) if $_[0] =~ /^_(:.*)?$/;
200 4 50       29 return unless shift =~ /^([a-z][a-z0-9]*)?([:_]([^:]+))?$/;
201 4 100       21 my $ns = $self->{ defined $1 ? $1 : '' };
202 4 50       12 return unless defined $ns;
203 4 50       15 return $self->GET($ns) unless $3;
204 4         16 return $self->GET( $ns . $3 );
205             }
206              
207             sub AUTOLOAD {
208 52     52   21007 my $self = shift;
209 52 100       8243 return unless $AUTOLOAD =~ /^.*::([a-z][a-z0-9]*)?(_([^:]+)?)?$/;
210 23 100       110 return $self->BLANK( defined $3 ? "_:$3" : '_' ) unless $1;
    100          
211 21 50       71 my $ns = $self->{$1} or return;
212 21 100       67 my $local = defined $3 ? $3 : shift;
213 21 100       77 return $self->GET($ns) unless defined $local;
214 9         38 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__