File Coverage

blib/lib/XML/Checker/Parser.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package XML::Checker::Parser;
2 3     3   200139 use strict;
  3         8  
  3         170  
3 3     3   5898 use XML::Parser;
  0            
  0            
4             use XML::Checker;
5              
6             use vars qw( @ISA @InterceptedHandlers @SGML_SEARCH_PATH %URI_MAP
7             $_checker $_prevFAIL
8             $_Init $_Final $_Char $_Start $_End $_Element $_Attlist
9             $_Doctype $_Unparsed $_Notation $_Entity $_skipInsignifWS
10             $_EndOfDoc
11             );
12              
13             @ISA = qw( XML::Parser );
14              
15             @InterceptedHandlers = qw( Init Final Char Start End Element Attlist
16             Doctype Unparsed Notation Entity );
17              
18             # Where to search for external DTDs (in local file system)
19             @SGML_SEARCH_PATH = ();
20              
21             # Where to search for external DTDs as referred to by public ID in a
22             # statement, e.g. "-//W3C//DTD HTML 4.0//EN"
23             # E.g. it could map "-//W3C//DTD HTML 4.0//EN" to "file:/user/html.dtd"
24             %URI_MAP = ();
25              
26             sub new
27             {
28             my ($class, %args) = @_;
29              
30             my $super = new XML::Parser (%args);
31             $super->{Checker} = new XML::Checker (%args);
32              
33             my %handlers = %{$super->{Handlers}};
34              
35             # Don't need Comment handler - assuming comments are allowed anywhere
36             #?? What should Default handler do?
37             #?? Check XMLDecl, ExternEnt, Proc? No, for now.
38             #?? Add CdataStart, CdataEnd support?
39              
40             for (@InterceptedHandlers)
41             {
42             my $func = "XML::Checker::Parser::$_";
43             $handlers{$_} = \&$func;
44             }
45              
46             $super->{UserHandlers} = $super->{Handlers};
47             $super->{Handlers} = \%handlers;
48              
49             bless $super, $class;
50             }
51              
52             sub getChecker
53             {
54             $_[0]->{Checker}
55             }
56              
57             sub parse
58             {
59             my $self = shift;
60             my $uh = $self->{UserHandlers};
61              
62             local $_checker = $self->{Checker};
63              
64             local $_Init = $uh->{Init};
65             local $_Final = $uh->{Final};
66             local $_Start = $uh->{Start};
67             local $_End = $uh->{End};
68             local $_Char = $uh->{Char};
69             local $_Element = $uh->{'Element'};
70             local $_Attlist = $uh->{'Attlist'};
71             local $_Doctype = $uh->{Doctype};
72             local $_Unparsed = $uh->{Unparsed};
73             local $_Notation = $uh->{Notation};
74             local $_Entity = $uh->{Entity};
75              
76             local $_prevFAIL = $XML::Checker::FAIL;
77             local $XML::Checker::FAIL = \&fail_add_context;
78              
79             local $XML::Checker::INSIGNIF_WS = 0;
80             local $_skipInsignifWS = $self->{SkipInsignifWS};
81              
82             local $_EndOfDoc = 0;
83            
84             $self->SUPER::parse (@_);
85             }
86              
87             my $LWP_USER_AGENT;
88             sub set_LWP_UserAgent # static
89             {
90             $LWP_USER_AGENT = shift;
91             }
92              
93             sub load_URL # static
94             {
95             my ($url, $lwp_user_agent) = @_;
96             my $result;
97              
98             # Read the file from the web with LWP.
99             #
100             # Note that we read in the entire file, which may not be ideal
101             # for large files. LWP::UserAgent also provides a callback style
102             # request, which we could convert to a stream with a fork()...
103            
104             my $response;
105             eval
106             {
107             use LWP::UserAgent;
108            
109             my $ua = $lwp_user_agent;
110             unless (defined $ua)
111             {
112             unless (defined $LWP_USER_AGENT)
113             {
114             $LWP_USER_AGENT = LWP::UserAgent->new;
115            
116             # Load proxy settings from environment variables, i.e.:
117             # http_proxy, ftp_proxy, no_proxy etc. (see LWP::UserAgent(3))
118             # You need these to go thru firewalls.
119             $LWP_USER_AGENT->env_proxy;
120             }
121             $ua = $LWP_USER_AGENT;
122             }
123             my $req = new HTTP::Request 'GET', $url;
124             $response = $LWP_USER_AGENT->request ($req);
125             $result = $response->content;
126             };
127             if ($@)
128             {
129             die "Couldn't load URL [$url] with LWP: $@";
130             }
131             if (!$result)
132             {
133             my $message = $response->as_string;
134             die "Couldn't load URL [$url] with LWP: $message";
135             }
136             return $result;
137             }
138              
139             sub parsefile
140             {
141             my $self = shift;
142             my $url = shift;
143              
144             # Any other URL schemes?
145             if ($url =~ /^(https?|ftp|wais|gopher|file):/)
146             {
147             my $xml = load_URL ($url, $self->{LWP_UserAgent});
148             my $result;
149             eval
150             {
151             # Parse the result of the HTTP request
152             $result = $self->parse ($xml, @_);
153             };
154             if ($@)
155             {
156             die "Couldn't parsefile [$url]: $@";
157             }
158             return $result;
159             }
160             else
161             {
162             return $self->SUPER::parsefile ($url, @_);
163             }
164             }
165              
166             sub Init
167             {
168             my $expat = shift;
169             $_checker->{Expat} = $expat;
170              
171             $_checker->Init (@_);
172             &$_Init ($expat) if $_Init;
173             }
174              
175             sub Final
176             {
177             my $expat = shift;
178             $_EndOfDoc = 1;
179              
180             $_checker->Final (@_);
181             my $result = &$_Final ($expat) if $_Final;
182              
183             # Decouple Expat from Checker
184             delete $_checker->{Expat};
185              
186             # NOTE: Checker is not decoupled
187             return $result;
188             }
189              
190             sub Start
191             {
192             my ($expat, $tag, @attr) = @_;
193              
194             $_checker->Start ($tag);
195              
196             my $num_spec = $expat->specified_attr;
197             for (my $i = 0; $i < @attr; $i++)
198             {
199             my $spec = ($i < $num_spec);
200             my $attr = $attr[$i];
201             my $val = $attr[++$i];
202              
203             # print "--- $tag $attr $val $spec\n";
204             $_checker->Attr ($tag, $attr, $val, $spec);
205             }
206             $_checker->EndAttr;
207              
208             &$_Start ($expat, $tag, @attr) if $_Start;
209             }
210              
211             sub End
212             {
213             my $expat = shift;
214             $_checker->End (@_);
215             &$_End ($expat, @_) if $_End;
216             }
217              
218             sub Char
219             {
220             my $expat = shift;
221             $_checker->Char (@_);
222             &$_Char ($expat, @_)
223             if $_Char && !($XML::Checker::INSIGNIF_WS && $_skipInsignifWS);
224             # Skip insignificant whitespace
225             }
226              
227             sub Element
228             {
229             my $expat = shift;
230             $_checker->Element (@_);
231             &$_Element ($expat, @_) if $_Element;
232             }
233              
234             sub Attlist
235             {
236             my $expat = shift;
237             $_checker->Attlist (@_);
238             &$_Attlist ($expat, @_) if $_Attlist;
239             }
240              
241              
242             sub Doctype
243             {
244             my $expat = shift;
245             my ($name, $sysid, $pubid, $internal) = @_;
246              
247             my $dtd;
248             unless ($_checker->{SkipExternalDTD})
249             {
250             if ($sysid)
251             {
252             # External DTD...
253            
254             #?? I'm not sure if we should die here or keep going?
255             $dtd = load_DTD ($sysid, $expat->{LWP_UserAgent});
256             }
257             elsif ($pubid)
258             {
259             $dtd = load_DTD ($pubid, $expat->{LWP_UserAgent});
260             }
261             }
262              
263             if (defined $dtd)
264             {
265             #?? what about passing ProtocolEncoding, Namespaces, Stream_Delimiter ?
266             my $parser = new XML::Parser (
267             Checker => $_checker,
268             ErrorContext => $expat->{ErrorContext},
269             Handlers => {
270             Entity => \&XML::Checker::Parser::ExternalDTD::Entity,
271             Notation => \&XML::Checker::Parser::ExternalDTD::Notation,
272             Element => \&XML::Checker::Parser::ExternalDTD::Element,
273             Attlist => \&XML::Checker::Parser::ExternalDTD::Attlist,
274             Unparsed => \&XML::Checker::Parser::ExternalDTD::Unparsed,
275             });
276              
277             eval
278             {
279             $parser->parse ("\n<$name/>");
280             };
281             if ($@)
282             {
283             die "Couldn't parse contents of external DTD <$sysid> :$@";
284             }
285             }
286             $_checker->Doctype (@_);
287             &$_Doctype ($expat, @_) if $_Doctype;
288             }
289              
290             sub Unparsed
291             {
292             my $expat = shift;
293             $_checker->Unparsed (@_);
294             &$_Unparsed ($expat, @_) if $_Unparsed;
295             }
296              
297             sub Entity
298             {
299             my $expat = shift;
300             $_checker->Entity (@_);
301             &$_Entity ($expat, @_) if $_Entity;
302             }
303              
304             sub Notation
305             {
306             my $expat = shift;
307             $_checker->Notation (@_);
308             &$_Notation ($expat, @_) if $_Notation;
309             }
310              
311             sub Default
312             {
313             #?? what can I check here?
314             # print "Default handler got[" . join (", ", @_) . "]";
315             }
316              
317             #sub XMLDecl
318             #{
319             #?? support later?
320             #}
321              
322             sub setHandlers
323             {
324             my ($self, %h) = @_;
325             my (%oldhandlers);
326              
327             for my $name (@InterceptedHandlers)
328             {
329             if (exists $h{$name})
330             {
331             $oldhandlers{$name} = $self->{UserHandlers}->{$name};
332             $self->{UserHandlers}->{$name} = $h{$name};
333             delete $h{$name};
334             }
335             }
336              
337             # Pass remaining handlers to the parent class (XML::Parser)
338             return (%oldhandlers, $self->SUPER::setHandlers (%h));
339             }
340              
341             # Add (line, column, byte) to error context (unless it's EOF)
342             sub fail_add_context # static
343             {
344             my $e = $_checker->{Expat};
345              
346             my $byte = $e->current_byte; # -1 means: end of XML document
347             if ($byte != -1 && !$_EndOfDoc)
348             {
349             push @_, (line => $e->current_line,
350             column => $e->current_column,
351             byte => $byte);
352             }
353             &$_prevFAIL (@_);
354             }
355              
356             #-------- STATIC METHODS related to External DTDs ---------------------------
357              
358             sub load_DTD # static
359             {
360             my ($sysid, $lwp_user_agent) = @_;
361              
362             # See if it is defined in the %URI_MAP
363             # (Public IDs are stored here, e.g. "-//W3C//DTD HTML 4.0//EN")
364             if (exists $URI_MAP{$sysid})
365             {
366             $sysid = $URI_MAP{$sysid};
367             }
368             elsif ($sysid !~ /^\w+:/)
369             {
370             # Prefix the sysid with 'file:' if it has no protocol identifier
371             unless ($sysid =~ /^\//)
372             {
373             # Not an absolute path. See if it's in SGML_SEARCH_PATH.
374             my $relative_sysid = $sysid;
375              
376             $sysid = find_in_sgml_search_path ($sysid);
377             if (! $sysid)
378             {
379             if ($ENV{'SGML_SEARCH_PATH'})
380             {
381             die "Couldn't find external DTD [$relative_sysid] in SGML_SEARCH_PATH ($ENV{'SGML_SEARCH_PATH'})";
382             }
383             else
384             {
385             die "Couldn't find external DTD [$relative_sysid], may be you should set SGML_SEARCH_PATH";
386             }
387             }
388             }
389             $sysid = "file:$sysid";
390             }
391              
392             return load_URL ($sysid, $lwp_user_agent);
393             }
394              
395             sub map_uri # static
396             {
397             %URI_MAP = (%URI_MAP, @_);
398             }
399              
400             sub set_sgml_search_path # static
401             {
402             @SGML_SEARCH_PATH = @_;
403             }
404              
405             sub find_in_sgml_search_path # static
406             {
407             my $file = shift;
408              
409             my @dirs = @SGML_SEARCH_PATH;
410             unless (@dirs)
411             {
412             my $path = $ENV{SGML_SEARCH_PATH};
413             if ($path)
414             {
415             @dirs = split (':', $path);
416             }
417             else
418             {
419             my $home = $ENV{HOME};
420             @dirs = (".", "$home/.sgml", "/usr/lib/sgml", "/usr/share/sgml");
421             }
422             }
423              
424             for my $directory (@dirs)
425             {
426             if (-e "$directory/$file")
427             {
428             return "$directory/$file";
429             }
430             }
431             return undef;
432             }
433              
434             package XML::Checker::Parser::ExternalDTD;
435              
436             sub Element {
437             my $expat = shift;
438             $expat->{Checker}->Element(@_);
439             }
440              
441             sub Attlist {
442             my $expat = shift;
443             $expat->{Checker}->Attlist(@_);
444             }
445              
446             sub Unparsed {
447             my $expat = shift;
448             $expat->{Checker}->Unparsed(@_);
449             }
450              
451             sub Notation {
452             my $expat = shift;
453             $expat->{Checker}->Notation(@_);
454             }
455              
456             sub Entity {
457             my $expat = shift;
458             # print "Entity: $expat\n";
459             $expat->{Checker}->Entity(@_);
460             }
461              
462             sub my_final
463             {
464             return 1;
465             }
466              
467             1; # package return code
468              
469             __END__