File Coverage

blib/lib/RPC/XML/Parser/XMLParser.pm
Criterion Covered Total %
statement 119 121 98.3
branch n/a
condition n/a
subroutine 40 40 100.0
pod n/a
total 159 161 98.7


line stmt bran cond sub pod time code
1             ###############################################################################
2             #
3             # This file copyright (c) 2001-2011 Randy J. Ray, all rights reserved
4             #
5             # Copying and distribution are permitted under the terms of the Artistic
6             # License 2.0 (http://www.opensource.org/licenses/artistic-license-2.0.php) or
7             # the GNU LGPL (http://www.opensource.org/licenses/lgpl-2.1.php).
8             #
9             ###############################################################################
10             #
11             # Description: This is the RPC::XML::Parser::XMLParser class, a container
12             # for the XML::Parser class.
13             #
14             # Functions: new
15             # parse
16             # message_init
17             # message_end
18             # tag_start
19             # error
20             # stack_error
21             # tag_end
22             # char_data
23             # extern_ent
24             # final
25             #
26             # Libraries: RPC::XML
27             # XML::Parser
28             #
29             # Global Consts: Uses $RPC::XML::ERROR
30             #
31             # Environment: None.
32             #
33             ###############################################################################
34              
35             package RPC::XML::Parser::XMLParser;
36              
37 11     11   5480 use 5.008008;
  11         40  
  11         439  
38 11     11   64 use strict;
  11         42  
  11         379  
39 11     11   60 use warnings;
  11         19  
  11         403  
40 11     11   62 use vars qw($VERSION);
  11         31  
  11         644  
41 11         89 use subs qw(error stack_error new message_init message_end tag_start tag_end
42 11     11   1021 final char_data parse);
  11         45  
43 11     11   1214 use base 'RPC::XML::Parser';
  11         24  
  11         6946  
44              
45             # I'm not ready to add Readonly to my list of dependencies...
46             ## no critic (ProhibitConstantPragma)
47              
48             # These constants are only used by the internal stack machine
49 11     11   68 use constant PARSE_ERROR => 0;
  11         22  
  11         781  
50 11     11   58 use constant METHOD => 1;
  11         24  
  11         475  
51 11     11   55 use constant METHODSET => 2;
  11         23  
  11         465  
52 11     11   60 use constant RESPONSE => 3;
  11         21  
  11         481  
53 11     11   66 use constant RESPONSESET => 4;
  11         30  
  11         440  
54 11     11   59 use constant STRUCT => 5;
  11         20  
  11         514  
55 11     11   118 use constant ARRAY => 6;
  11         24  
  11         475  
56 11     11   75 use constant DATATYPE => 7;
  11         19  
  11         501  
57 11     11   84 use constant ATTR_SET => 8;
  11         43  
  11         501  
58 11     11   54 use constant METHODNAME => 9;
  11         23  
  11         572  
59 11     11   54 use constant VALUEMARKER => 10;
  11         29  
  11         468  
60 11     11   63 use constant PARAMSTART => 11;
  11         19  
  11         482  
61 11     11   59 use constant PARAM => 12;
  11         21  
  11         485  
62 11     11   57 use constant PARAMENT => 13;
  11         19  
  11         543  
63 11     11   56 use constant STRUCTMEM => 14;
  11         26  
  11         536  
64 11     11   60 use constant STRUCTNAME => 15;
  11         17  
  11         564  
65 11     11   263 use constant DATAOBJECT => 16;
  11         21  
  11         516  
66 11     11   61 use constant PARAMLIST => 17;
  11         23  
  11         551  
67 11     11   61 use constant NAMEVAL => 18;
  11         20  
  11         557  
68 11     11   57 use constant MEMBERENT => 19;
  11         19  
  11         541  
69 11     11   57 use constant METHODENT => 20;
  11         36  
  11         491  
70 11     11   59 use constant RESPONSEENT => 21;
  11         21  
  11         492  
71 11     11   59 use constant FAULTENT => 22;
  11         24  
  11         499  
72 11     11   98 use constant FAULTSTART => 23;
  11         19  
  11         502  
73 11     11   57 use constant DATASTART => 24;
  11         24  
  11         855  
74              
75             # This is to identify valid types
76 11         38 use constant VALIDTYPES => { map { ($_, 1) } qw(int i4 i8 string double
  99         1982  
77             boolean dateTime.iso8601
78 11     11   60 base64 nil) };
  11         23  
79             # This maps XML tags to stack-machine tokens
80 11         692 use constant TAG2TOKEN => { methodCall => METHOD,
81             methodResponse => RESPONSE,
82             methodName => METHODNAME,
83             params => PARAMSTART,
84             param => PARAM,
85             value => VALUEMARKER,
86             fault => FAULTSTART,
87             array => ARRAY,
88             data => DATASTART,
89             struct => STRUCT,
90             member => STRUCTMEM,
91 11     11   61 name => STRUCTNAME };
  11         23  
92              
93             # Members of the class
94 11     11   61 use constant M_STACK => 0;
  11         22  
  11         537  
95 11     11   232 use constant M_CDATA => 1;
  11         37  
  11         533  
96 11     11   59 use constant M_BASE64_TO_FH => 2;
  11         19  
  11         509  
97 11     11   56 use constant M_BASE64_TEMP_DIR => 3;
  11         25  
  11         500  
98 11     11   57 use constant M_SPOOLING_BASE64_DATA => 4;
  11         19  
  11         565  
99              
100 11     11   288 use Scalar::Util 'reftype';
  11         27  
  11         827  
101 11     11   8373 use XML::Parser;
  0            
  0            
102              
103             require RPC::XML;
104              
105             $VERSION = '1.28';
106             $VERSION = eval $VERSION; ## no critic (ProhibitStringyEval)
107              
108             ###############################################################################
109             #
110             # Sub Name: new
111             #
112             # Description: Constructor. Save any important attributes, leave the
113             # heavy lifting for the parse() routine and XML::Parser.
114             #
115             # Arguments: NAME IN/OUT TYPE DESCRIPTION
116             # $class in scalar Class we're initializing
117             # %attr in hash Any extras the caller wants
118             #
119             # Globals: $RPC::XML::ERROR
120             #
121             # Returns: Success: object ref
122             # Failure: undef
123             #
124             ###############################################################################
125             sub new
126             {
127             my ($class, %attrs) = @_;
128              
129             my $self = [];
130              
131             while (my ($key, $val) = each %attrs)
132             {
133             if ($key eq 'base64_to_fh')
134             {
135             $self->[M_BASE64_TO_FH] = $val;
136             }
137             elsif ($key eq 'base64_temp_dir')
138             {
139             $self->[M_BASE64_TEMP_DIR] = $val;
140             }
141             }
142              
143             return bless $self, $class;
144             }
145              
146             ###############################################################################
147             #
148             # Sub Name: parse
149             #
150             # Description: Parse the requested string or stream. This behaves mostly
151             # like parse() in the XML::Parser namespace, but does some
152             # extra, as well.
153             #
154             # Arguments: NAME IN/OUT TYPE DESCRIPTION
155             # $self in ref Object of this class
156             # $stream in scalar Either the string to parse or
157             # an open filehandle of sorts
158             #
159             # Returns: Success: ref to request or response object
160             # Failure: error string
161             #
162             ###############################################################################
163             sub parse
164             {
165             my ($self, $stream) = @_;
166              
167             my $parser = XML::Parser->new(
168             Namespaces => 0,
169             ParseParamEnt => 0,
170             ErrorContext => 1,
171             Handlers => {
172             Init => sub { message_init $self, @_ },
173             Start => sub { tag_start $self, @_ },
174             End => sub { tag_end $self, @_ },
175             Char => sub { char_data $self, @_ },
176             Final => sub { final $self, @_ },
177             ExternEnt => sub { extern_ent $self, @_ },
178             }
179             );
180              
181             # If there is no stream given, then create an incremental parser handle
182             # and return it.
183             # RT58323: It's not enough to just test $stream, I have to check
184             # defined-ness. A 0 or null-string should yield an error, not a push-parser
185             # instance.
186             if (! defined $stream)
187             {
188             return $parser->parse_start();
189             }
190              
191             # If the user passed a scalar ref, dereference it. This is to provide
192             # feature parity with the XML::LibXML-based parser.
193             if ((ref $stream) && (reftype($stream) eq 'SCALAR'))
194             {
195             $stream = ${$stream};
196             }
197              
198             # If it is now any type of reference other than GLOB, we can't parse it
199             if ((ref $stream) && (reftype($stream) ne 'GLOB'))
200             {
201             return "Unusable reference type '$stream'";
202             }
203              
204             my $retval;
205             if (! eval { $retval = $parser->parse($stream); 1; })
206             {
207             return "Parse error: $@";
208             }
209              
210             return $retval;
211             }
212              
213             # This is called when a new document is about to start parsing
214             sub message_init
215             {
216             my ($robj, $self) = @_;
217              
218             $robj->[M_STACK] = [];
219              
220             return $self;
221             }
222              
223             # This is called when the parsing process is complete. There is a second arg,
224             # $self, that is passed but not used. So it isn't declared for now.
225             sub final
226             {
227             my ($robj) = @_;
228              
229             # Look at the top-most marker, it'll need to be one of the end cases
230             my $marker = pop @{$robj->[M_STACK]};
231             # There should be one item on the stack after it (except in error cases)
232             my $retval = pop @{$robj->[M_STACK]};
233              
234             # The marker has to be one of these three values, or else we didn't parse a
235             # valid XML-RPC document:
236             if (! (($marker == PARSE_ERROR) || ($marker == METHODENT) ||
237             ($marker == RESPONSEENT)))
238             {
239             $retval = 'End-of-parse error: No error, methodCall or ' .
240             'methodResponse detected';
241             }
242              
243             return $retval;
244             }
245              
246             # This gets called each time an opening tag is parsed. In addition to the three
247             # args here, any attributes are passed in hash form as well. But the XML-RPC
248             # spec uses no attributes, so we aren't declaring them here as the list will
249             # (or should, at least) always be empty.
250             sub tag_start
251             {
252             my ($robj, $self, $elem) = @_;
253              
254             $robj->[M_CDATA] = [];
255              
256             if (TAG2TOKEN->{$elem})
257             {
258             push @{$robj->[M_STACK]}, TAG2TOKEN->{$elem};
259             }
260             elsif (VALIDTYPES->{$elem})
261             {
262             # All datatypes are represented on the stack by this generic token
263             push @{$robj->[M_STACK]}, DATATYPE;
264             # If the tag is and we've been told to use filehandles, set
265             # that up.
266             if (($elem eq 'base64') && $robj->[M_BASE64_TO_FH])
267             {
268             require File::Spec;
269             require File::Temp;
270             my $fh;
271             my $tmpdir = File::Spec->tmpdir;
272              
273             if ($robj->[M_BASE64_TEMP_DIR])
274             {
275             $tmpdir = $robj->[M_BASE64_TEMP_DIR];
276             }
277             $fh = eval { File::Temp->new(UNLINK => 1, DIR => $tmpdir) };
278             if (! $fh)
279             {
280             push @{$robj->[M_STACK]},
281             "Error opening temp file for base64: $@", PARSE_ERROR;
282             $self->finish;
283             }
284             $robj->[M_CDATA] = $fh;
285             $robj->[M_SPOOLING_BASE64_DATA]= 1;
286             }
287             }
288             else
289             {
290             push @{$robj->[M_STACK]},
291             "Unknown tag encountered: $elem", PARSE_ERROR;
292             $self->finish;
293             }
294              
295             return;
296             }
297              
298             # Very simple error-text generator, just to eliminate heavy reduncancy in the
299             # next sub:
300             sub error
301             {
302             my ($robj, $self, $mesg, $elem) = @_;
303             $elem ||= q{};
304              
305             my $fmt = $elem ?
306             '%s at document line %d, column %d (byte %d, closing tag %s)' :
307             '%s at document line %d, column %d (byte %d)';
308              
309             push @{$robj->[M_STACK]},
310             sprintf($fmt, $mesg, $self->current_line, $self->current_column,
311             $self->current_byte, $elem),
312             PARSE_ERROR;
313             $self->finish;
314              
315             return;
316             }
317              
318             # A shorter-cut for stack integrity errors
319             sub stack_error
320             {
321             my ($robj, $self, $elem) = @_;
322              
323             return error($robj, $self, 'Stack corruption detected', $elem);
324             }
325              
326             # This is a hairy subroutine-- what to do at the end-tag. The actions range
327             # from simply new-ing a datatype all the way to building the final object.
328             sub tag_end ## no critic (ProhibitExcessComplexity)
329             {
330             my ($robj, $self, $elem) = @_;
331              
332             my ($op, $newobj, $class, $list, $name);
333              
334             # This should always be one of the stack machine ops defined above
335             $op = pop @{$robj->[M_STACK]};
336              
337             my $cdata = q{};
338             if ($robj->[M_SPOOLING_BASE64_DATA])
339             {
340             $cdata = $robj->[M_CDATA];
341             seek $cdata, 0, 0;
342             }
343             elsif ($robj->[M_CDATA])
344             {
345             $cdata = join q{} => @{$robj->[M_CDATA]};
346             }
347              
348             # Decide what to do from here
349             if (VALIDTYPES->{$elem}) ## no critic (ProhibitCascadingIfElse)
350             {
351             # This is the closing tag of one of the data-types.
352             $class = $elem;
353             # Cheaper than the regex that was here, and more locale-portable
354             if ($class eq 'dateTime.iso8601')
355             {
356             $class = 'datetime_iso8601';
357             }
358             # Some minimal data-integrity checking
359             if ($class eq 'int' or $class eq 'i4' or $class eq 'i8')
360             {
361             if ($cdata !~ /^[-+]?\d+$/)
362             {
363             return error($robj, $self, 'Bad integer data read');
364             }
365             }
366             elsif ($class eq 'double')
367             {
368             if ($cdata !~
369             # Taken from perldata(1)
370             /^[+-]?(?=\d|[.]\d)\d*(?:[.]\d*)?(?:[Ee](?:[+-]?\d+))?$/x)
371             {
372             return error($robj, $self, 'Bad floating-point data read');
373             }
374             }
375             elsif ($class eq 'nil')
376             {
377             # We now allow parsing of at all times.
378             # By definition though, it must be, well... nil.
379             if ($cdata !~ /^\s*$/)
380             {
381             return error($robj, $self, ' element must be empty');
382             }
383             }
384              
385             $class = "RPC::XML::$class";
386             # The string at the end is only seen by the RPC::XML::base64 class
387             $newobj = $class->new($cdata, 'base64 is encoded, nil is allowed');
388             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
389             if ($robj->[M_SPOOLING_BASE64_DATA])
390             {
391             $robj->[M_SPOOLING_BASE64_DATA] = 0;
392             $robj->[M_CDATA] = undef; # Won't close FH, $newobj still holds it
393             }
394             }
395             elsif ($elem eq 'value')
396             {
397             # For , there should already be a dataobject, or else
398             # the marker token in which case the CDATA is used as a string value.
399             if ($op == DATAOBJECT)
400             {
401             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
402             if ($op != VALUEMARKER)
403             {
404             return stack_error($robj, $self, $elem);
405             }
406             }
407             else
408             {
409             $newobj = RPC::XML::string->new($cdata);
410             }
411              
412             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
413             }
414             elsif ($elem eq 'param')
415             {
416             # Almost like above, since this is really a NOP anyway. But it also
417             # puts PARAMENT on the stack, so that the closing tag of
418             # can check for bad content.
419             if ($op != DATAOBJECT)
420             {
421             return error($robj, $self,
422             'No found within container');
423             }
424             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
425             if ($op != PARAM)
426             {
427             return error($robj, $self, "Illegal content in $elem tag");
428             }
429             push @{$robj->[M_STACK]}, $newobj, PARAMENT;
430             }
431             elsif ($elem eq 'params')
432             {
433             # At this point, there should be zero or more PARAMENT tokens on the
434             # stack, each with an object right below it.
435             $list = [];
436             if ($op != PARAMENT && $op != PARAMSTART)
437             {
438             return error($robj, $self, "Illegal content in $elem tag");
439             }
440             while ($op == PARAMENT)
441             {
442             unshift @{$list}, pop @{$robj->[M_STACK]};
443             $op = pop @{$robj->[M_STACK]};
444             }
445             # Now that we see something ! PARAMENT, it needs to be PARAMSTART
446             if ($op != PARAMSTART)
447             {
448             return error($robj, $self, "Illegal content in $elem tag");
449             }
450             push @{$robj->[M_STACK]}, $list, PARAMLIST;
451             }
452             elsif ($elem eq 'fault')
453             {
454             # If we're finishing up a fault definition, there needs to be a struct
455             # on the stack.
456             if ($op != DATAOBJECT)
457             {
458             return stack_error($robj, $self, $elem);
459             }
460             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
461             if (! $newobj->isa('RPC::XML::struct'))
462             {
463             return error($robj, $self,
464             'Only a value may be within a ');
465             }
466             $newobj = RPC::XML::fault->new($newobj);
467             if (! $newobj)
468             {
469             return error($robj, $self, 'Unable to instantiate fault object: ' .
470             $RPC::XML::ERROR);
471             }
472              
473             push @{$robj->[M_STACK]}, $newobj, FAULTENT;
474             }
475             elsif ($elem eq 'member')
476             {
477             # We need to see a DATAOBJECT followed by a STRUCTNAME
478             if ($op != DATAOBJECT)
479             {
480             return error(
481             $robj, $self, 'Element mismatch, expected to see value'
482             );
483             }
484             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
485             if ($op != STRUCTNAME)
486             {
487             return error(
488             $robj, $self, 'Element mismatch, expected to see name'
489             );
490             }
491             # Get the name off the stack to clear the way for the STRUCTMEM marker
492             # under it
493             ($op, $name) = splice @{$robj->[M_STACK]}, -2;
494             # Push the name back on, with the value and the new marker (STRUCTMEM)
495             push @{$robj->[M_STACK]}, $name, $newobj, STRUCTMEM;
496             }
497             elsif ($elem eq 'name')
498             {
499             # Fairly simple: just push the current content of CDATA on w/ a marker
500             push @{$robj->[M_STACK]}, $cdata, STRUCTNAME;
501             }
502             elsif ($elem eq 'struct')
503             {
504             # Create the hash table in-place, then pass the ref to the constructor
505             $list = {};
506             # First off the stack needs to be STRUCTMEM or STRUCT
507             if (! ($op == STRUCTMEM or $op == STRUCT))
508             {
509             return error(
510             $robj, $self, 'Element mismatch, expected to see member'
511             );
512             }
513             while ($op == STRUCTMEM)
514             {
515             # Next on stack (in list-order): name, value
516             ($name, $newobj) = splice @{$robj->[M_STACK]}, -2;
517             $list->{$name} = $newobj;
518             $op = pop @{$robj->[M_STACK]};
519             }
520             # Now that we see something ! STRUCTMEM, it needs to be STRUCT
521             if ($op != STRUCT)
522             {
523             return error($robj, $self, 'Bad content inside struct block');
524             }
525             $newobj = RPC::XML::struct->new($list);
526              
527             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
528             }
529             elsif ($elem eq 'data')
530             {
531             # The block within an declaration serves
532             # to gather together all the elements that will make up the
533             # resulting list.
534             #
535             # Go down the stack, gathering DATAOBJECT markers until we see the
536             # DATASTART marker.
537             $list = [];
538             # Only DATAOBJECT and DATASTART should be visible
539             if ($op != DATASTART && $op != DATAOBJECT)
540             {
541             return error($robj, $self, 'Bad content inside data block');
542             }
543             while ($op == DATAOBJECT)
544             {
545             unshift @{$list}, pop @{$robj->[M_STACK]};
546             $op = pop @{$robj->[M_STACK]};
547             }
548              
549             # Now that we see something ! DATAOBJECT, it needs to be DATASTART
550             if ($op != DATASTART)
551             {
552             return error($robj, $self, "Illegal content in $elem tag");
553             }
554              
555             # We might as well instantiate the RPC::XML::array object here, and
556             # put it on the stack with a DATAOBJECT marker. Then the end-tag of
557             # the can just look to make sure there is exactly one
558             # DATAOBJECT/value pair between it and the start of the array.
559             $newobj = RPC::XML::array->new(from => $list);
560              
561             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
562             }
563             elsif ($elem eq 'array')
564             {
565             # Now that we process the block directly (I used to just
566             # ignore it), handling the closing tag of is just a matter
567             # of making sure $op is DATAOBJECT and that we have an array object
568             # on the stack with an ARRAY marker just below it.
569              
570             # Only DATAOBJECT or ARRAY should be visible
571             if ($op == DATAOBJECT)
572             {
573             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
574             }
575              
576             # Now only ARRAY should be
577             if ($op != ARRAY)
578             {
579             return error($robj, $self, "Illegal content in $elem tag");
580             }
581              
582             # Technically, this is a little redundant, since we had these two right
583             # here on the stack when we started. But at this point we've validated
584             # the form of the block and removed the ARRAY marker from the
585             # stack.
586             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
587             }
588             elsif ($elem eq 'methodName')
589             {
590             if ($robj->[M_STACK]->[$#{$robj->[M_STACK]}] != METHOD)
591             {
592             return error(
593             $robj, $self,
594             "$elem tag must immediately follow a methodCall tag"
595             );
596             }
597             push @{$robj->[M_STACK]}, $cdata, NAMEVAL;
598             }
599             elsif ($elem eq 'methodCall')
600             {
601             # A methodCall closing should have on the stack an optional PARAMLIST
602             # marker, a NAMEVAL marker, then the METHOD token from the
603             # opening tag.
604             if ($op == PARAMLIST)
605             {
606             ($op, $list) = splice @{$robj->[M_STACK]}, -2;
607             }
608             else
609             {
610             $list = [];
611             }
612             if ($op == NAMEVAL)
613             {
614             ($op, $name) = splice @{$robj->[M_STACK]}, -2;
615             }
616             elsif ($op != METHOD)
617             {
618             return error(
619             $robj, $self,
620             'Extra content in "methodCall" block detected'
621             );
622             }
623             if (! $name)
624             {
625             return error(
626             $robj, $self,
627             'No methodName tag detected during methodCall parsing'
628             );
629             }
630              
631             # Create the request object and push it on the stack
632             $newobj = RPC::XML::request->new($name, @{$list});
633             if (! $newobj)
634             {
635             return error($robj, $self,
636             "Error creating request object: $RPC::XML::ERROR");
637             }
638              
639             push @{$robj->[M_STACK]}, $newobj, METHODENT;
640             }
641             elsif ($elem eq 'methodResponse')
642             {
643             # A methodResponse closing should have on the stack only the
644             # DATAOBJECT marker, then the RESPONSE token from the opening tag.
645             if ($op == PARAMLIST)
646             {
647             # To my knowledge, the XML-RPC spec limits the params list for
648             # a response to exactly one object. Extract it from the listref
649             # and put it back.
650             $list = pop @{$robj->[M_STACK]};
651             if (@{$list} > 1)
652             {
653             return error(
654             $robj, $self,
655             "Params list for $elem tag invalid: too many params"
656             );
657             }
658             elsif (@{$list} == 0)
659             {
660             return error(
661             $robj, $self,
662             "Params list for $elem tag invalid: no params"
663             );
664             }
665             push @{$robj->[M_STACK]}, $list->[0];
666             }
667             elsif ($op != DATAOBJECT && $op != FAULTENT)
668             {
669             return error($robj, $self,
670             "No parameter was declared for the $elem tag");
671             }
672             ($op, $list) = splice @{$robj->[M_STACK]}, -2;
673             if ($op != RESPONSE)
674             {
675             return stack_error($robj, $self, $elem);
676             }
677              
678             # Create the response object and push it on the stack
679             $newobj = RPC::XML::response->new($list);
680             push @{$robj->[M_STACK]}, $newobj, RESPONSEENT;
681             }
682              
683             return;
684             }
685              
686             # This just spools the character data until a closing tag makes use of it
687             sub char_data
688             {
689             my ($robj, undef, $characters) = @_;
690              
691             if ($robj->[M_SPOOLING_BASE64_DATA])
692             {
693             print {$robj->[M_CDATA]} $characters;
694             }
695             else
696             {
697             push @{$robj->[M_CDATA]}, $characters;
698             }
699              
700             return;
701             }
702              
703             # At some future point, this may be expanded to provide more entities than
704             # just the basic XML ones.
705             sub extern_ent
706             {
707             return q{};
708             }
709              
710             # Exception-throwing stub in case this is called without first getting the
711             # XML::Parser::ExpatNB instance:
712             sub parse_more
713             {
714             die __PACKAGE__ . '::parse_more: Must be called on a push-parser ' .
715             "instance obtained from parse()\n";
716             }
717              
718             # Exception-throwing stub in case this is called without first getting the
719             # XML::Parser::ExpatNB instance:
720             sub parse_done
721             {
722             die __PACKAGE__ . '::parse_done: Must be called on a push-parser ' .
723             "instance obtained from parse()\n";
724             }
725              
726             1;
727              
728             __END__