File Coverage

blib/lib/XML/Catalog.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package XML::Catalog;
2            
3 1     1   52053 use strict;
  1         2  
  1         48  
4 1     1   6 use warnings;
  1         2  
  1         31  
5            
6 1     1   560 use XML::Parser;
  0            
  0            
7             use LWP::Simple;
8             use URI::URL;
9             use Text::ParseWords;
10            
11             ## no critic
12             our $VERSION = "1.03";
13             $VERSION = eval $VERSION;
14             ## use critic
15            
16             #####################################################################
17             # Class variables (private)
18             #####################################################################
19            
20             # hash of catalog objects indexed by URL (to prevent loops in construction)
21             my %catobjs;
22            
23             # hash of catalog objects visited in current search, indexed by
24             # stringified reference (to prevent loops in search)
25             use vars qw/%visited/;
26            
27             #####################################################################
28             # Constructor
29             #####################################################################
30            
31             sub new {
32             my $class = shift;
33             my $url = shift || die "No catalog specified";
34             my $cat = build( $class, $url ) or return;
35             $cat->add(@_);
36             return $cat;
37             }
38            
39             #####################################################################
40             # Public methods
41             #####################################################################
42            
43             sub add {
44             my $self = shift;
45             foreach my $url (@_) {
46             my $cat = build( $self, $url );
47             $self->add_extend_object( $cat, 'chain' ) if defined $cat;
48             }
49             }
50            
51             sub resolve_public {
52             my ( $self, $pubid ) = @_;
53             %visited = ();
54            
55             # try without delegation
56             my $rm = $self->_resolve_public( $pubid, 0 );
57             unless ($rm) {
58            
59             # try with delegation
60             %visited = ();
61             $rm = $self->_resolve_public( $pubid, 1 );
62             }
63             return $self->remap_system($rm) if defined $rm;
64             return;
65             }
66            
67             sub remap_system {
68             my ( $self, $sysid ) = @_;
69             %visited = ();
70             my $rm = $self->_remap_system($sysid);
71             return ( defined($rm) ? $rm : $sysid );
72             }
73            
74             sub get_handler {
75             my ( $catalog, $parser ) = @_;
76             my ( $t, $orig_handler ) = $parser->setHandlers( ExternEnt => 0 );
77             return sub {
78             my ( $expat, $base, $sysid, $pubid ) = @_;
79             if ($pubid) {
80             my $t = $catalog->resolve_public($pubid);
81             $sysid = $t if $t;
82             }
83             $sysid = $catalog->remap_system($sysid);
84             $orig_handler->( $expat, $base, $sysid, $pubid );
85             }
86             }
87            
88             #####################################################################
89             # Private methods and subs
90             #####################################################################
91            
92             # create new catalog object
93            
94             sub build {
95             my ( $c, $url ) = @_;
96             return $catobjs{$url} if defined $catobjs{$url};
97             my $class = ref($c) || $c;
98             my $self = {
99             url => $url, #needed?
100             base => $url,
101             map => {},
102             remap => {},
103             delegate => {},
104             extend => [],
105             chain => []
106             };
107             bless( $self, $class );
108             $catobjs{$url} = $self;
109             $self->parse;
110             return $self;
111             }
112            
113             # add another catalog object as extension to ours
114            
115             sub add_extend_object {
116             my ( $self, $cat, $group ) = @_;
117             foreach my $t ( @{ $self->{$group} } ) {
118             return if $t == $cat;
119             }
120             push @{ $self->{$group} }, $cat;
121             }
122            
123             # translate public identifier with or without delegation, checking for loops
124             sub _resolve_public {
125             my ( $self, $pubid, $allowdeleg ) = @_;
126             return if $visited{$self};
127             $visited{$self} = 1;
128             if ($allowdeleg) {
129             local %visited;
130             foreach my $deleg ( keys %{ $self->{delegate} } ) {
131             if ( index( $pubid, $deleg ) == 0 ) {
132             my $rm = $self->{delegate}{$deleg}->resolve_public($pubid);
133             return $rm if defined $rm;
134             }
135             }
136             }
137             else {
138             return $self->{map}{$pubid} if defined $self->{map}{$pubid};
139             }
140            
141             # no local match, try chained and extend entries
142             foreach my $group (qw/chain extend/) {
143             foreach my $cat ( @{ $self->{$group} } ) {
144             my $rm = $cat->_resolve_public( $pubid, $allowdeleg );
145             return $rm if defined $rm;
146             }
147             }
148             return;
149             }
150            
151             # translate system identifier, checking for loops
152             sub _remap_system {
153             my ( $self, $sysid ) = @_;
154             return if $visited{$self};
155             return $self->{remap}{$sysid} if defined $self->{remap}{$sysid};
156             $visited{$self} = 1;
157             foreach my $group (qw/chain extend/) {
158             foreach my $cat ( @{ $self->{$group} } ) {
159             my $rm = $cat->_remap_system($sysid);
160             return $rm if defined $rm;
161             }
162             }
163             return;
164             }
165            
166             sub parse {
167             my $self = shift;
168             my $u = new URI::URL( $self->{url} );
169             $u->scheme('file') unless $u->scheme();
170             my $url = $u->as_string();
171             my $ct = LWP::Simple::get($url);
172             die "Unable to retrieve URL: [$url]" unless defined $ct;
173             $self->parse_SOCAT($ct) unless $self->parse_XML($ct);
174             }
175            
176             sub fix_base {
177             my ( $self, $url ) = @_;
178             my $u = new URI::URL( $url, $self->{base} );
179             return $u->abs->as_string();
180             }
181            
182             sub add_map {
183             my ( $self, $pubid, $href ) = @_;
184             $self->{map}{$pubid} = $self->fix_base($href);
185             }
186            
187             sub add_remap {
188             my ( $self, $sysid, $href ) = @_;
189             $self->{remap}{$sysid} = $self->fix_base($href);
190             }
191            
192             sub add_delegate {
193             my ( $self, $pubid, $href ) = @_;
194             my $cat = build( $self, $self->fix_base($href) );
195             $self->{delegate}{$pubid} = $cat if defined $cat;
196             }
197            
198             sub add_extend {
199             my ( $self, $href ) = @_;
200             $href = $self->fix_base($href);
201             my $cat = build( $self, $href );
202             $self->add_extend_object( $cat, 'extend' ) if defined $cat;
203             }
204            
205             sub set_base {
206             my ( $self, $href ) = @_;
207             $self->{base} = $self->fix_base($href);
208             }
209            
210             sub parse_SOCAT {
211             my ( $token, $sysid, $pubid, $href );
212             my ( $self, $ct ) = @_;
213            
214             #backslashes are allowed; change them to forward slashes
215             $ct =~ s#\\#/#g;
216            
217             #strip comments
218             $ct =~ s/((['"]).*\2|.*)--.*?--/$1/g;
219             my @tokens = quotewords( '\s+', 0, $ct );
220             while ( defined( $token = shift @tokens ) ) {
221             if ( uc $token eq 'PUBLIC' ) {
222             defined( $pubid = shift @tokens ) or return 0;
223             defined( $href = shift @tokens ) or return 0;
224             $self->add_map( $pubid, $href );
225             }
226             elsif ( uc $token eq 'SYSTEM' ) {
227             defined( $sysid = shift @tokens ) or return 0;
228             defined( $href = shift @tokens ) or return 0;
229             $self->add_remap( $sysid, $href );
230             }
231             elsif ( uc $token eq 'DELEGATE' ) {
232             defined( $pubid = shift @tokens ) or return 0;
233             defined( $href = shift @tokens ) or return 0;
234             $self->add_delegate( $pubid, $href );
235             }
236             elsif ( uc $token eq 'CATALOG' ) {
237             defined( $href = shift @tokens ) or return 0;
238             $self->add_extend($href);
239             }
240             elsif ( uc $token eq 'BASE' ) {
241             defined( $href = shift @tokens ) or return 0;
242             $self->set_base($href);
243             }
244             else {
245             next;
246             }
247             }
248             return 1;
249             }
250            
251             sub parse_XML {
252             my ( $self, $ct ) = @_;
253             my $p = new XML::Parser( Style => 'Subs', Pkg => 'XML::Catalog::XML' );
254             $p->{XMLCatalog} = $self;
255             eval { $p->parse($ct) };
256             return ( $@ ? 0 : 1 );
257             }
258            
259             package XML::Catalog::XML;
260            
261             ## no critic
262             our $VERSION = "1.02";
263             $VERSION = eval $VERSION;
264             ## use critic
265            
266             sub Map {
267             my ( $p, $elem, %attrs ) = @_;
268             my $self = $p->{XMLCatalog};
269             $self->add_map( $attrs{PublicId}, $attrs{HRef} );
270             }
271            
272             sub Remap {
273             my ( $p, $elem, %attrs ) = @_;
274             my $self = $p->{XMLCatalog};
275             $self->add_remap( $attrs{SystemId}, $attrs{HRef} );
276             }
277            
278             sub Delegate {
279             my ( $p, $elem, %attrs ) = @_;
280             my $self = $p->{XMLCatalog};
281             $self->add_delegate( $attrs{PublicId}, $attrs{HRef} );
282             }
283            
284             sub Extend {
285             my ( $p, $elem, %attrs ) = @_;
286             my $self = $p->{XMLCatalog};
287             $self->add_extend( $attrs{HRef} );
288             }
289            
290             sub Base {
291             my ( $p, $elem, %attrs ) = @_;
292             my $self = $p->{XMLCatalog};
293             $self->set_base( $attrs{HRef} );
294             }
295            
296             # Oasis XML catalog support
297             sub catalog {
298             }
299            
300             sub rewriteURI {
301             my ( $p, $elem, %attrs ) = @_;
302             my $self = $p->{XMLCatalog};
303             $self->add_delegate( $attrs{uriStartString}, $attrs{rewritePrefix} );
304             }
305            
306             sub rewriteSystem {
307             my ( $p, $elem, %attrs ) = @_;
308             my $self = $p->{XMLCatalog};
309             $self->add_delegate( $attrs{systemIdStartString}, $attrs{rewritePrefix} );
310             }
311            
312             sub public {
313             my ( $p, $elem, %attrs ) = @_;
314             my $self = $p->{XMLCatalog};
315             $self->add_map( $attrs{publicId}, $attrs{uri} );
316             }
317            
318             sub delegatePublic {
319             my ( $p, $elem, %attrs ) = @_;
320             my $self = $p->{XMLCatalog};
321             $self->add_extend( $attrs{catalog} );
322             }
323            
324             sub delegateSystem {
325             my ( $p, $elem, %attrs ) = @_;
326             my $self = $p->{XMLCatalog};
327             $self->add_extend( $attrs{catalog} );
328             }
329            
330             sub delegateURI {
331             my ( $p, $elem, %attrs ) = @_;
332             my $self = $p->{XMLCatalog};
333             $self->add_extend( $attrs{catalog} );
334             }
335            
336             sub nextCatalog {
337             my ( $p, $elem, %attrs ) = @_;
338             my $self = $p->{XMLCatalog};
339             $self->add_extend( $attrs{catalog} );
340             }
341            
342             sub system {
343             my ( $p, $elem, %attrs ) = @_;
344             my $self = $p->{XMLCatalog};
345             $self->add_delegate( $attrs{systemId}, $attrs{uri} );
346             }
347            
348             1;
349             __END__