File Coverage

blib/lib/RPC/XML/Parser/XMLParser.pm
Criterion Covered Total %
statement 118 120 98.3
branch n/a
condition n/a
subroutine 40 40 100.0
pod n/a
total 158 160 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   17580 use 5.008008;
  11         26  
38 11     11   43 use strict;
  11         19  
  11         199  
39 11     11   35 use warnings;
  11         13  
  11         272  
40 11     11   36 use vars qw($VERSION);
  11         13  
  11         488  
41 11         57 use subs qw(error stack_error new message_init message_end tag_start tag_end
42 11     11   451 final char_data parse);
  11         28  
43 11     11   928 use base 'RPC::XML::Parser';
  11         13  
  11         3923  
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   46 use constant PARSE_ERROR => 0;
  11         87  
  11         642  
50 11     11   39 use constant METHOD => 1;
  11         13  
  11         460  
51 11     11   34 use constant METHODSET => 2;
  11         10  
  11         369  
52 11     11   34 use constant RESPONSE => 3;
  11         13  
  11         347  
53 11     11   31 use constant RESPONSESET => 4;
  11         12  
  11         356  
54 11     11   36 use constant STRUCT => 5;
  11         11  
  11         394  
55 11     11   35 use constant ARRAY => 6;
  11         11  
  11         361  
56 11     11   33 use constant DATATYPE => 7;
  11         13  
  11         365  
57 11     11   69 use constant ATTR_SET => 8;
  11         15  
  11         358  
58 11     11   31 use constant METHODNAME => 9;
  11         11  
  11         342  
59 11     11   32 use constant VALUEMARKER => 10;
  11         13  
  11         335  
60 11     11   34 use constant PARAMSTART => 11;
  11         13  
  11         339  
61 11     11   36 use constant PARAM => 12;
  11         12  
  11         363  
62 11     11   36 use constant PARAMENT => 13;
  11         10  
  11         366  
63 11     11   35 use constant STRUCTMEM => 14;
  11         11  
  11         361  
64 11     11   36 use constant STRUCTNAME => 15;
  11         15  
  11         375  
65 11     11   139 use constant DATAOBJECT => 16;
  11         17  
  11         359  
66 11     11   34 use constant PARAMLIST => 17;
  11         8  
  11         390  
67 11     11   43 use constant NAMEVAL => 18;
  11         11  
  11         425  
68 11     11   32 use constant MEMBERENT => 19;
  11         16  
  11         336  
69 11     11   32 use constant METHODENT => 20;
  11         10  
  11         966  
70 11     11   32 use constant RESPONSEENT => 21;
  11         14  
  11         388  
71 11     11   34 use constant FAULTENT => 22;
  11         12  
  11         362  
72 11     11   32 use constant FAULTSTART => 23;
  11         14  
  11         350  
73 11     11   31 use constant DATASTART => 24;
  11         10  
  11         535  
74              
75             # This is to identify valid types
76 11         19 use constant VALIDTYPES => { map { ($_, 1) } qw(int i4 i8 string double
  99         809  
77             boolean dateTime.iso8601
78 11     11   35 base64 nil) };
  11         8  
79             # This maps XML tags to stack-machine tokens
80 11         439 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   40 name => STRUCTNAME };
  11         13  
92              
93             # Members of the class
94 11     11   34 use constant M_STACK => 0;
  11         13  
  11         636  
95 11     11   34 use constant M_CDATA => 1;
  11         13  
  11         338  
96 11     11   29 use constant M_BASE64_TO_FH => 2;
  11         10  
  11         336  
97 11     11   32 use constant M_BASE64_TEMP_DIR => 3;
  11         13  
  11         381  
98 11     11   36 use constant M_SPOOLING_BASE64_DATA => 4;
  11         10  
  11         371  
99              
100 11     11   35 use Scalar::Util 'reftype';
  11         13  
  11         531  
101 11     11   4028 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             my $msg;
304              
305             if ($elem)
306             {
307             $msg = sprintf
308             '%s at document line %d, column %d (byte %d, closing tag %s)',
309             $mesg, $self->current_line, $self->current_column,
310             $self->current_byte, $elem;
311             }
312             else
313             {
314             $msg = sprintf '%s at document line %d, column %d (byte %d)',
315             $mesg, $self->current_line, $self->current_column,
316             $self->current_byte;
317             }
318              
319             push @{$robj->[M_STACK]}, $msg, PARSE_ERROR;
320             $self->finish;
321              
322             return;
323             }
324              
325             # A shorter-cut for stack integrity errors
326             sub stack_error
327             {
328             my ($robj, $self, $elem) = @_;
329              
330             return error($robj, $self, 'Stack corruption detected', $elem);
331             }
332              
333             # This is a hairy subroutine-- what to do at the end-tag. The actions range
334             # from simply new-ing a datatype all the way to building the final object.
335             sub tag_end ## no critic (ProhibitExcessComplexity)
336             {
337             my ($robj, $self, $elem) = @_;
338              
339             my ($op, $newobj, $class, $list, $name);
340              
341             # This should always be one of the stack machine ops defined above
342             $op = pop @{$robj->[M_STACK]};
343              
344             my $cdata = q{};
345             if ($robj->[M_SPOOLING_BASE64_DATA])
346             {
347             $cdata = $robj->[M_CDATA];
348             seek $cdata, 0, 0;
349             }
350             elsif ($robj->[M_CDATA])
351             {
352             $cdata = join q{} => @{$robj->[M_CDATA]};
353             }
354              
355             # Decide what to do from here
356             if (VALIDTYPES->{$elem}) ## no critic (ProhibitCascadingIfElse)
357             {
358             # This is the closing tag of one of the data-types.
359             $class = $elem;
360             # Cheaper than the regex that was here, and more locale-portable
361             if ($class eq 'dateTime.iso8601')
362             {
363             $class = 'datetime_iso8601';
364             }
365             # Some minimal data-integrity checking
366             if ($class eq 'int' or $class eq 'i4' or $class eq 'i8')
367             {
368             if ($cdata !~ /^[-+]?\d+$/)
369             {
370             return error($robj, $self, 'Bad integer data read');
371             }
372             }
373             elsif ($class eq 'double')
374             {
375             if ($cdata !~
376             # Taken from perldata(1)
377             /^[+-]?(?=\d|[.]\d)\d*(?:[.]\d*)?(?:[Ee](?:[+-]?\d+))?$/x)
378             {
379             return error($robj, $self, 'Bad floating-point data read');
380             }
381             }
382             elsif ($class eq 'nil')
383             {
384             # We now allow parsing of at all times.
385             # By definition though, it must be, well... nil.
386             if ($cdata !~ /^\s*$/)
387             {
388             return error($robj, $self, ' element must be empty');
389             }
390             }
391              
392             $class = "RPC::XML::$class";
393             # The string at the end is only seen by the RPC::XML::base64 class
394             $newobj = $class->new($cdata, 'base64 is encoded, nil is allowed');
395             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
396             if ($robj->[M_SPOOLING_BASE64_DATA])
397             {
398             $robj->[M_SPOOLING_BASE64_DATA] = 0;
399             $robj->[M_CDATA] = undef; # Won't close FH, $newobj still holds it
400             }
401             }
402             elsif ($elem eq 'value')
403             {
404             # For , there should already be a dataobject, or else
405             # the marker token in which case the CDATA is used as a string value.
406             if ($op == DATAOBJECT)
407             {
408             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
409             if ($op != VALUEMARKER)
410             {
411             return stack_error($robj, $self, $elem);
412             }
413             }
414             else
415             {
416             $newobj = RPC::XML::string->new($cdata);
417             }
418              
419             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
420             }
421             elsif ($elem eq 'param')
422             {
423             # Almost like above, since this is really a NOP anyway. But it also
424             # puts PARAMENT on the stack, so that the closing tag of
425             # can check for bad content.
426             if ($op != DATAOBJECT)
427             {
428             return error($robj, $self,
429             'No found within container');
430             }
431             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
432             if ($op != PARAM)
433             {
434             return error($robj, $self, "Illegal content in $elem tag");
435             }
436             push @{$robj->[M_STACK]}, $newobj, PARAMENT;
437             }
438             elsif ($elem eq 'params')
439             {
440             # At this point, there should be zero or more PARAMENT tokens on the
441             # stack, each with an object right below it.
442             $list = [];
443             if ($op != PARAMENT && $op != PARAMSTART)
444             {
445             return error($robj, $self, "Illegal content in $elem tag");
446             }
447             while ($op == PARAMENT)
448             {
449             unshift @{$list}, pop @{$robj->[M_STACK]};
450             $op = pop @{$robj->[M_STACK]};
451             }
452             # Now that we see something ! PARAMENT, it needs to be PARAMSTART
453             if ($op != PARAMSTART)
454             {
455             return error($robj, $self, "Illegal content in $elem tag");
456             }
457             push @{$robj->[M_STACK]}, $list, PARAMLIST;
458             }
459             elsif ($elem eq 'fault')
460             {
461             # If we're finishing up a fault definition, there needs to be a struct
462             # on the stack.
463             if ($op != DATAOBJECT)
464             {
465             return stack_error($robj, $self, $elem);
466             }
467             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
468             if (! $newobj->isa('RPC::XML::struct'))
469             {
470             return error($robj, $self,
471             'Only a value may be within a ');
472             }
473             $newobj = RPC::XML::fault->new($newobj);
474             if (! $newobj)
475             {
476             return error($robj, $self, 'Unable to instantiate fault object: ' .
477             $RPC::XML::ERROR);
478             }
479              
480             push @{$robj->[M_STACK]}, $newobj, FAULTENT;
481             }
482             elsif ($elem eq 'member')
483             {
484             # We need to see a DATAOBJECT followed by a STRUCTNAME
485             if ($op != DATAOBJECT)
486             {
487             return error(
488             $robj, $self, 'Element mismatch, expected to see value'
489             );
490             }
491             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
492             if ($op != STRUCTNAME)
493             {
494             return error(
495             $robj, $self, 'Element mismatch, expected to see name'
496             );
497             }
498             # Get the name off the stack to clear the way for the STRUCTMEM marker
499             # under it
500             ($op, $name) = splice @{$robj->[M_STACK]}, -2;
501             # Push the name back on, with the value and the new marker (STRUCTMEM)
502             push @{$robj->[M_STACK]}, $name, $newobj, STRUCTMEM;
503             }
504             elsif ($elem eq 'name')
505             {
506             # Fairly simple: just push the current content of CDATA on w/ a marker
507             push @{$robj->[M_STACK]}, $cdata, STRUCTNAME;
508             }
509             elsif ($elem eq 'struct')
510             {
511             # Create the hash table in-place, then pass the ref to the constructor
512             $list = {};
513             # First off the stack needs to be STRUCTMEM or STRUCT
514             if (! ($op == STRUCTMEM or $op == STRUCT))
515             {
516             return error(
517             $robj, $self, 'Element mismatch, expected to see member'
518             );
519             }
520             while ($op == STRUCTMEM)
521             {
522             # Next on stack (in list-order): name, value
523             ($name, $newobj) = splice @{$robj->[M_STACK]}, -2;
524             $list->{$name} = $newobj;
525             $op = pop @{$robj->[M_STACK]};
526             }
527             # Now that we see something ! STRUCTMEM, it needs to be STRUCT
528             if ($op != STRUCT)
529             {
530             return error($robj, $self, 'Bad content inside struct block');
531             }
532             $newobj = RPC::XML::struct->new($list);
533              
534             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
535             }
536             elsif ($elem eq 'data')
537             {
538             # The block within an declaration serves
539             # to gather together all the elements that will make up the
540             # resulting list.
541             #
542             # Go down the stack, gathering DATAOBJECT markers until we see the
543             # DATASTART marker.
544             $list = [];
545             # Only DATAOBJECT and DATASTART should be visible
546             if ($op != DATASTART && $op != DATAOBJECT)
547             {
548             return error($robj, $self, 'Bad content inside data block');
549             }
550             while ($op == DATAOBJECT)
551             {
552             unshift @{$list}, pop @{$robj->[M_STACK]};
553             $op = pop @{$robj->[M_STACK]};
554             }
555              
556             # Now that we see something ! DATAOBJECT, it needs to be DATASTART
557             if ($op != DATASTART)
558             {
559             return error($robj, $self, "Illegal content in $elem tag");
560             }
561              
562             # We might as well instantiate the RPC::XML::array object here, and
563             # put it on the stack with a DATAOBJECT marker. Then the end-tag of
564             # the can just look to make sure there is exactly one
565             # DATAOBJECT/value pair between it and the start of the array.
566             $newobj = RPC::XML::array->new(from => $list);
567              
568             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
569             }
570             elsif ($elem eq 'array')
571             {
572             # Now that we process the block directly (I used to just
573             # ignore it), handling the closing tag of is just a matter
574             # of making sure $op is DATAOBJECT and that we have an array object
575             # on the stack with an ARRAY marker just below it.
576              
577             # Only DATAOBJECT or ARRAY should be visible
578             if ($op == DATAOBJECT)
579             {
580             ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
581             }
582              
583             # Now only ARRAY should be
584             if ($op != ARRAY)
585             {
586             return error($robj, $self, "Illegal content in $elem tag");
587             }
588              
589             # Technically, this is a little redundant, since we had these two right
590             # here on the stack when we started. But at this point we've validated
591             # the form of the block and removed the ARRAY marker from the
592             # stack.
593             push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
594             }
595             elsif ($elem eq 'methodName')
596             {
597             if ($robj->[M_STACK]->[$#{$robj->[M_STACK]}] != METHOD)
598             {
599             return error(
600             $robj, $self,
601             "$elem tag must immediately follow a methodCall tag"
602             );
603             }
604             push @{$robj->[M_STACK]}, $cdata, NAMEVAL;
605             }
606             elsif ($elem eq 'methodCall')
607             {
608             # A methodCall closing should have on the stack an optional PARAMLIST
609             # marker, a NAMEVAL marker, then the METHOD token from the
610             # opening tag.
611             if ($op == PARAMLIST)
612             {
613             ($op, $list) = splice @{$robj->[M_STACK]}, -2;
614             }
615             else
616             {
617             $list = [];
618             }
619             if ($op == NAMEVAL)
620             {
621             ($op, $name) = splice @{$robj->[M_STACK]}, -2;
622             }
623             elsif ($op != METHOD)
624             {
625             return error(
626             $robj, $self,
627             'Extra content in "methodCall" block detected'
628             );
629             }
630             if (! $name)
631             {
632             return error(
633             $robj, $self,
634             'No methodName tag detected during methodCall parsing'
635             );
636             }
637              
638             # Create the request object and push it on the stack
639             $newobj = RPC::XML::request->new($name, @{$list});
640             if (! $newobj)
641             {
642             return error($robj, $self,
643             "Error creating request object: $RPC::XML::ERROR");
644             }
645              
646             push @{$robj->[M_STACK]}, $newobj, METHODENT;
647             }
648             elsif ($elem eq 'methodResponse')
649             {
650             # A methodResponse closing should have on the stack only the
651             # DATAOBJECT marker, then the RESPONSE token from the opening tag.
652             if ($op == PARAMLIST)
653             {
654             # To my knowledge, the XML-RPC spec limits the params list for
655             # a response to exactly one object. Extract it from the listref
656             # and put it back.
657             $list = pop @{$robj->[M_STACK]};
658             if (@{$list} > 1)
659             {
660             return error(
661             $robj, $self,
662             "Params list for $elem tag invalid: too many params"
663             );
664             }
665             elsif (@{$list} == 0)
666             {
667             return error(
668             $robj, $self,
669             "Params list for $elem tag invalid: no params"
670             );
671             }
672             push @{$robj->[M_STACK]}, $list->[0];
673             }
674             elsif ($op != DATAOBJECT && $op != FAULTENT)
675             {
676             return error($robj, $self,
677             "No parameter was declared for the $elem tag");
678             }
679             ($op, $list) = splice @{$robj->[M_STACK]}, -2;
680             if ($op != RESPONSE)
681             {
682             return stack_error($robj, $self, $elem);
683             }
684              
685             # Create the response object and push it on the stack
686             $newobj = RPC::XML::response->new($list);
687             push @{$robj->[M_STACK]}, $newobj, RESPONSEENT;
688             }
689              
690             return;
691             }
692              
693             # This just spools the character data until a closing tag makes use of it
694             sub char_data
695             {
696             my ($robj, undef, $characters) = @_;
697              
698             if ($robj->[M_SPOOLING_BASE64_DATA])
699             {
700             print {$robj->[M_CDATA]} $characters;
701             }
702             else
703             {
704             push @{$robj->[M_CDATA]}, $characters;
705             }
706              
707             return;
708             }
709              
710             # At some future point, this may be expanded to provide more entities than
711             # just the basic XML ones.
712             sub extern_ent
713             {
714             return q{};
715             }
716              
717             # Exception-throwing stub in case this is called without first getting the
718             # XML::Parser::ExpatNB instance:
719             sub parse_more
720             {
721             die __PACKAGE__ . '::parse_more: Must be called on a push-parser ' .
722             "instance obtained from parse()\n";
723             }
724              
725             # Exception-throwing stub in case this is called without first getting the
726             # XML::Parser::ExpatNB instance:
727             sub parse_done
728             {
729             die __PACKAGE__ . '::parse_done: Must be called on a push-parser ' .
730             "instance obtained from parse()\n";
731             }
732              
733             1;
734              
735             __END__