File Coverage

blib/lib/XML/Parser/PerlSAX.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             #
2             # Copyright (C) 1999 Ken MacLeod
3             # XML::Parser::PerlSAX is free software; you can redistribute it and/or
4             # modify it under the same terms as Perl itself.
5             #
6             # $Id: PerlSAX.pm,v 1.7 1999/12/22 21:15:00 kmacleod Exp $
7             #
8              
9 6     6   7597 use strict;
  6         13  
  6         347  
10              
11             package XML::Parser::PerlSAX;
12              
13 6     6   15626 use XML::Parser;
  0            
  0            
14             use UNIVERSAL;
15             use vars qw{ $VERSION $name_re };
16              
17             # will be substituted by make-rel script
18             $VERSION = "0.08";
19              
20             # FIXME I doubt this is a correct Perl RE for productions [4] and
21             # [5] in the XML 1.0 specification, especially considering Unicode chars
22             $name_re = '[A-Za-z_:][A-Za-z0-9._:-]*';
23              
24             sub new {
25             my $type = shift;
26             my $self = (@_ == 1) ? shift : { @_ };
27              
28             return bless $self, $type;
29             }
30              
31             sub parse {
32             my $self = shift;
33              
34             die "XML::Parser::PerlSAX: parser instance ($self) already parsing\n"
35             if (defined $self->{ParseOptions});
36              
37             # If there's one arg and it has no ref, it's a string
38             my $args;
39             if (scalar (@_) == 1 && !ref($_[0])) {
40             $args = { Source => { String => shift } };
41             } else {
42             $args = (scalar (@_) == 1) ? shift : { @_ };
43             }
44              
45             my $parse_options = { %$self, %$args };
46             $self->{ParseOptions} = $parse_options;
47              
48             # ensure that we have at least one source
49             if (!defined $parse_options->{Source}
50             || !(defined $parse_options->{Source}{String}
51             || defined $parse_options->{Source}{ByteStream}
52             || defined $parse_options->{Source}{SystemId})) {
53             die "XML::Parser::PerlSAX: no source defined for parse\n";
54             }
55              
56             # assign default Handler to any undefined handlers
57             if (defined $parse_options->{Handler}) {
58             $parse_options->{DocumentHandler} = $parse_options->{Handler}
59             if (!defined $parse_options->{DocumentHandler});
60             $parse_options->{DTDHandler} = $parse_options->{Handler}
61             if (!defined $parse_options->{DTDHandler});
62             $parse_options->{EntityResolver} = $parse_options->{Handler}
63             if (!defined $parse_options->{EntityResolver});
64             }
65              
66             my @handlers;
67             if (defined $parse_options->{DocumentHandler}) {
68             # cache DocumentHandler in self for callbacks
69             $self->{DocumentHandler} = $parse_options->{DocumentHandler};
70              
71             my $doc_h = $parse_options->{DocumentHandler};
72              
73             push (@handlers, Init => sub { $self->_handle_init(@_) } )
74             if (UNIVERSAL::can($doc_h, 'start_document'));
75             push (@handlers, Final => sub { $self->_handle_final(@_) } )
76             if (UNIVERSAL::can($doc_h, 'end_document'));
77             push (@handlers, Start => sub { $self->_handle_start(@_) } )
78             if (UNIVERSAL::can($doc_h, 'start_element'));
79             push (@handlers, End => sub { $self->_handle_end(@_) } )
80             if (UNIVERSAL::can($doc_h, 'end_element'));
81             push (@handlers, Char => sub { $self->_handle_char(@_) } )
82             if (UNIVERSAL::can($doc_h, 'characters'));
83             push (@handlers, Proc => sub { $self->_handle_proc(@_) } )
84             if (UNIVERSAL::can($doc_h, 'processing_instruction'));
85             push (@handlers, Comment => sub { $self->_handle_comment(@_) } )
86             if (UNIVERSAL::can($doc_h, 'comment'));
87             push (@handlers, CdataStart => sub { $self->_handle_cdatastart(@_) } )
88             if (UNIVERSAL::can($doc_h, 'start_cdata'));
89             push (@handlers, CdataEnd => sub { $self->_handle_cdataend(@_) } )
90             if (UNIVERSAL::can($doc_h, 'end_cdata'));
91             if (UNIVERSAL::can($doc_h, 'entity_reference')) {
92             push (@handlers, Default => sub { $self->_handle_default(@_) } );
93             $self->{UseEntRefs} = 1;
94             }
95             }
96              
97             if (defined $parse_options->{DTDHandler}) {
98             # cache DTDHandler in self for callbacks
99             $self->{DTDHandler} = $parse_options->{DTDHandler};
100              
101             my $dtd_h = $parse_options->{DTDHandler};
102              
103             push (@handlers, Notation => sub { $self->_handle_notation(@_) } )
104             if (UNIVERSAL::can($dtd_h, 'notation_decl'));
105             push (@handlers, Unparsed => sub { $self->_handle_unparsed(@_) } )
106             if (UNIVERSAL::can($dtd_h, 'unparsed_entity_decl'));
107             push (@handlers, Entity => sub { $self->_handle_entity(@_) } )
108             if ($self->{UseEntRefs}
109             || UNIVERSAL::can($dtd_h, 'entity_decl'));
110             push (@handlers, Element => sub { $self->_handle_element(@_) } )
111             if (UNIVERSAL::can($dtd_h, 'element_decl'));
112             push (@handlers, Attlist => sub { $self->_handle_attlist(@_) } )
113             if (UNIVERSAL::can($dtd_h, 'attlist_decl'));
114             push (@handlers, Doctype => sub { $self->_handle_doctype(@_) } )
115             if (UNIVERSAL::can($dtd_h, 'doctype_decl'));
116             push (@handlers, XMLDecl => sub { $self->_handle_xmldecl(@_) } )
117             if (UNIVERSAL::can($dtd_h, 'xml_decl'));
118             }
119              
120            
121             if (defined $parse_options->{EntityResolver}) {
122             # cache EntityResolver in self for callbacks
123             $self->{EntityResolver} = $parse_options->{EntityResolver};
124              
125             my $er = $parse_options->{EntityResolver};
126              
127             push (@handlers, ExternEnt => sub { $self->_handle_extern_ent(@_) } )
128             if (UNIVERSAL::can($er, 'resolve_entity'));
129             }
130              
131             my @xml_parser_options;
132             if ($self->{UseEntRefs}) {
133             @xml_parser_options = ( NoExpand => 1,
134             Handlers => { @handlers } );
135             } else {
136             @xml_parser_options = ( Handlers => { @handlers } );
137             }
138              
139             push (@xml_parser_options,
140             ProtocolEncoding => $self->{ParseOptions}{Source}{Encoding})
141             if (defined $self->{ParseOptions}{Source}{Encoding});
142              
143             my $parser = new XML::Parser(@xml_parser_options);
144             my $result;
145              
146             if (defined $self->{ParseOptions}{Source}{ByteStream}) {
147             $result = $parser->parse($self->{ParseOptions}{Source}{ByteStream});
148             } elsif (defined $self->{ParseOptions}{Source}{String}) {
149             $result = $parser->parse($self->{ParseOptions}{Source}{String});
150             } elsif (defined $self->{ParseOptions}{Source}{SystemId}) {
151             $result = $parser->parsefile($self->{ParseOptions}{Source}{SystemId});
152             }
153              
154             # clean up parser instance
155             delete $self->{ParseOptions};
156             delete $self->{DocumentHandler};
157             delete $self->{DTDHandler};
158             delete $self->{EntityResolver};
159             delete $self->{Expat};
160              
161             return $result;
162             }
163              
164             sub location {
165             my $self = shift;
166              
167             my $expat = $self->{Expat};
168              
169             my @properties = ( ColumnNumber => $expat->current_column,
170             LineNumber => $expat->current_line,
171             BytePosition => $expat->current_byte,
172             Base => $expat->base );
173              
174             # FIXME these locations change while parsing external entities
175             push (@properties, PublicId => $self->{Source}{PublicId})
176             if (defined $self->{Source}{PublicId});
177             push (@properties, SystemId => $self->{Source}{SystemId})
178             if (defined $self->{Source}{SystemId});
179              
180             return { @properties };
181             }
182              
183             ###
184             ### DocumentHandler methods
185             ###
186              
187             sub _handle_init {
188             my $self = shift;
189             my $expat = shift;
190              
191             $self->{Expat} = $expat;
192              
193             if ($self->{DocumentHandler}->can('set_document_locator')) {
194             $self->{DocumentHandler}->set_document_locator( { Locator => $self } );
195             }
196             $self->{DocumentHandler}->start_document( { } );
197             }
198              
199             sub _handle_final {
200             my $self = shift;
201              
202             delete $self->{UseEntRefs};
203             delete $self->{EntRefs};
204             return $self->{DocumentHandler}->end_document( { } );
205             }
206              
207             sub _handle_start {
208             my $self = shift;
209             my $expat = shift;
210             my $element = shift;
211              
212             my @properties;
213             if ($self->{ParseOptions}{UseAttributeOrder}) {
214             # Capture order and defined() status for attributes
215             my $ii;
216              
217             my $order = [];
218             for ($ii = 0; $ii < $#_; $ii += 2) {
219             push @$order, $_[$ii];
220             }
221              
222             push @properties, 'AttributeOrder', $order;
223              
224             # Divide by two because XML::Parser counts both attribute name
225             # and value within it's index
226             push @properties, 'Defaulted', ($expat->specified_attr() / 2);
227             }
228              
229             $self->{DocumentHandler}->start_element( { Name => $element,
230             Attributes => { @_ },
231             @properties } );
232             }
233              
234             sub _handle_end {
235             my $self = shift;
236             my $expat = shift;
237             my $element = shift;
238              
239             $self->{DocumentHandler}->end_element( { Name => $element } );
240             }
241              
242             sub _handle_char {
243             my $self = shift;
244             my $expat = shift;
245             my $string = shift;
246              
247             $self->{DocumentHandler}->characters( { Data => $string } );
248             }
249              
250             sub _handle_proc {
251             my $self = shift;
252             my $expat = shift;
253             my $target = shift;
254             my $data = shift;
255              
256             $self->{DocumentHandler}->processing_instruction( { Target => $target,
257             Data => $data } );
258             }
259              
260             sub _handle_comment {
261             my $self = shift;
262             my $expat = shift;
263             my $data = shift;
264              
265             $self->{DocumentHandler}->comment( { Data => $data } );
266             }
267              
268             sub _handle_cdatastart {
269             my $self = shift;
270             my $expat = shift;
271              
272             $self->{DocumentHandler}->start_cdata( { } );
273             }
274              
275             sub _handle_cdataend {
276             my $self = shift;
277             my $expat = shift;
278              
279             $self->{DocumentHandler}->end_cdata( { } );
280             }
281              
282             # Default receives all characters that aren't handled by some other
283             # handler, this means a lot of stuff goes through here. All we're
284             # looking for are `&NAME;' entity reference sequences
285             sub _handle_default {
286             my $self = shift;
287             my $expat = shift;
288             my $string = shift;
289              
290             if ($string =~ /^&($name_re);$/) {
291             my $ent_ref = $self->{EntRefs}{$1};
292             if (!defined $ent_ref) {
293             $ent_ref = { Name => $1 };
294             }
295             $self->{DocumentHandler}->entity_reference($ent_ref);
296             }
297             }
298              
299             ###
300             ### DTDHandler methods
301             ###
302              
303             sub _handle_notation {
304             my $self = shift;
305             my $expat = shift;
306             my $notation = shift;
307             my $base = shift;
308             my $sysid = shift;
309             my $pubid = shift;
310             my @properties = (Name => $notation);
311              
312             push (@properties, Base => $base)
313             if (defined $base);
314             push (@properties, SystemId => $sysid)
315             if (defined $sysid);
316             push (@properties, PublicId => $pubid)
317             if (defined $pubid);
318              
319              
320             $self->{DTDHandler}->notation_decl( { @properties } );
321             }
322              
323             sub _handle_unparsed {
324             my $self = shift;
325             my $expat = shift;
326             my $entity = shift;
327             my $base = shift;
328             my $sysid = shift;
329             my $pubid = shift;
330             my @properties = (Name => $entity, SystemId => $sysid);
331              
332             push (@properties, Base => $base)
333             if (defined $base);
334             push (@properties, PublicId => $pubid)
335             if (defined $pubid);
336              
337             $self->{DTDHandler}->unparsed_entity_decl( { @properties } );
338             }
339              
340             sub _handle_entity {
341             my $self = shift;
342             my $expat = shift;
343             my $name = shift;
344             my $val = shift;
345             my $sysid = shift;
346             my $pubid = shift;
347             my $ndata = shift;
348             my @properties = (Name => $name);
349              
350             push (@properties, Value => $val)
351             if (defined $val);
352             push (@properties, PublicId => $pubid)
353             if (defined $pubid);
354             push (@properties, SystemId => $sysid)
355             if (defined $sysid);
356             push (@properties, Notation => $ndata)
357             if (defined $ndata);
358              
359             my $properties = { @properties };
360             if ($self->{UseEntRefs}) {
361             $self->{EntRefs}{$name} = $properties;
362             }
363             if ($self->{DTDHandler}->can('entity_decl')) {
364             $self->{DTDHandler}->entity_decl( $properties );
365             }
366             }
367              
368             sub _handle_element {
369             my $self = shift;
370             my $expat = shift;
371             my $name = shift;
372             my $model = shift;
373              
374             $self->{DTDHandler}->element_decl( { Name => $name,
375             Model => $model } );
376             }
377              
378             sub _handle_attlist {
379             my $self = shift;
380             my $expat = shift;
381             my $elname = shift;
382             my $attname = shift;
383             my $type = shift;
384             my $default = shift;
385             my $fixed = shift;
386              
387             $self->{DTDHandler}->attlist_decl( { ElementName => $elname,
388             AttributeName => $attname,
389             Type => $type,
390             Default => $default,
391             Fixed => $fixed } );
392             }
393              
394             sub _handle_doctype {
395             my $self = shift;
396             my $expat = shift;
397             my $name = shift;
398             my $sysid = shift;
399             my $pubid = shift;
400             my $internal = shift;
401             my @properties = (Name => $name);
402              
403             push (@properties, SystemId => $sysid)
404             if (defined $sysid);
405             push (@properties, PublicId => $pubid)
406             if (defined $pubid);
407             push (@properties, Internal => $internal)
408             if (defined $internal);
409              
410             $self->{DTDHandler}->doctype_decl( { @properties } );
411             }
412              
413             sub _handle_xmldecl {
414             my $self = shift;
415             my $expat = shift;
416             my $version = shift;
417             my $encoding = shift;
418             my $standalone = shift;
419             my @properties = (Version => $version);
420              
421             push (@properties, Encoding => $encoding)
422             if (defined $encoding);
423             push (@properties, Standalone => $standalone)
424             if (defined $standalone);
425              
426             $self->{DTDHandler}->xml_decl( { @properties } );
427             }
428              
429             ###
430             ### EntityResolver methods
431             ###
432              
433             sub _handle_extern_ent {
434             my $self = shift;
435             my $expat = shift;
436             my $base = shift;
437             my $sysid = shift;
438             my $pubid = shift;
439             my @properties = (SystemId => $sysid);
440              
441             push (@properties, Base => $base)
442             if (defined $base);
443             push (@properties, PublicId => $pubid)
444             if (defined $pubid);
445              
446             my $result = $self->{EntityResolver}->resolve_entity( { @properties } );
447              
448             if (UNIVERSAL::isa($result, 'HASH')) {
449             if ($result->{ByteStream}) {
450             return $result->{ByteStream};
451             } elsif ($result->{String}) {
452             return $result->{String};
453             } elsif ($result->{SystemId}) {
454             # FIXME must be able to resolve SystemIds, XML::Parser's
455             # default can :-(
456             die "PerlSAX: automatic opening of SystemIds from \`resolve_entity' not implemented, contact the author\n";
457             } else {
458             # FIXME
459             die "PerlSAX: invalid source returned from \`resolve_entity'\n";
460             }
461             }
462              
463             return undef;
464             }
465              
466             1;
467              
468             __END__