1package XML::SAX::Writer;
2$XML::SAX::Writer::VERSION = '0.57';
3use strict;
4use warnings;
5use vars qw(%DEFAULT_ESCAPE %ATTRIBUTE_ESCAPE %COMMENT_ESCAPE);
6
7# ABSTRACT: SAX2 XML Writer
8
9use Encode                  qw();
10use XML::SAX::Exception     qw();
11use XML::SAX::Writer::XML   qw();
12use XML::Filter::BufferText qw();
13@XML::SAX::Writer::Exception::ISA = qw(XML::SAX::Exception);
14
15
16%DEFAULT_ESCAPE = (
17                    '&'     => '&',
18                    '<'     => '&lt;',
19                    '>'     => '&gt;',
20                    '"'     => '&quot;',
21                    "'"     => '&apos;',
22                  );
23
24%ATTRIBUTE_ESCAPE = (
25                    %DEFAULT_ESCAPE,
26                    "\t"    => '&#x9;',
27                    "\n"    => '&#xA;',
28                    "\r"    => '&#xD;',
29                  );
30
31%COMMENT_ESCAPE = (
32                    '--'    => '&#45;&#45;',
33                  );
34
35
36#-------------------------------------------------------------------#
37# new
38#-------------------------------------------------------------------#
39sub new {
40    my $class = ref($_[0]) ? ref(shift) : shift;
41    my $opt   = (@_ == 1)  ? { %{shift()} } : {@_};
42
43    # default the options
44    $opt->{Writer}          ||= 'XML::SAX::Writer::XML';
45    $opt->{Escape}          ||= \%DEFAULT_ESCAPE;
46    $opt->{AttributeEscape} ||= \%ATTRIBUTE_ESCAPE;
47    $opt->{CommentEscape}   ||= \%COMMENT_ESCAPE;
48    $opt->{EncodeFrom}        = exists $opt->{EncodeFrom} ? $opt->{EncodeFrom} : 'utf-8';
49    $opt->{EncodeTo}          = exists $opt->{EncodeTo}   ? $opt->{EncodeTo}   : 'utf-8';
50    $opt->{Format}          ||= {}; # needs options w/ defaults, we'll see later
51    $opt->{Output}          ||= *{STDOUT}{IO};
52    $opt->{QuoteCharacter}  ||= q['];
53
54    eval "use $opt->{Writer};";
55
56    my $obj = bless $opt, $opt->{Writer};
57    $obj->init;
58
59    # we need to buffer the text to escape it right
60    my $bf = XML::Filter::BufferText->new( Handler => $obj );
61
62    return $bf;
63}
64#-------------------------------------------------------------------#
65
66#-------------------------------------------------------------------#
67# init
68#-------------------------------------------------------------------#
69sub init {} # noop, for subclasses
70#-------------------------------------------------------------------#
71
72#-------------------------------------------------------------------#
73# setConverter
74#-------------------------------------------------------------------#
75sub setConverter {
76    my $self = shift;
77
78    if (lc($self->{EncodeFrom}) ne lc($self->{EncodeTo})) {
79        $self->{Encoder} = XML::SAX::Writer::Encode->new($self->{EncodeFrom}, $self->{EncodeTo});
80    }
81    else {
82        $self->{Encoder} = XML::SAX::Writer::NullConverter->new;
83    }
84    return $self;
85}
86#-------------------------------------------------------------------#
87
88#-------------------------------------------------------------------#
89# setConsumer
90#-------------------------------------------------------------------#
91sub setConsumer {
92    my $self = shift;
93
94    # create the Consumer
95    my $ref = ref $self->{Output};
96    if ($ref eq 'SCALAR') {
97        $self->{Consumer} = XML::SAX::Writer::StringConsumer->new($self->{Output});
98    }
99    elsif ($ref eq 'CODE') {
100        $self->{Consumer} = XML::SAX::Writer::CodeConsumer->new($self->{Output});
101    }
102    elsif ($ref eq 'ARRAY') {
103        $self->{Consumer} = XML::SAX::Writer::ArrayConsumer->new($self->{Output});
104    }
105    elsif (
106            $ref eq 'GLOB'                                or
107            UNIVERSAL::isa(\$self->{Output}, 'GLOB')      or
108            UNIVERSAL::isa($self->{Output}, 'IO::Handle')) {
109        $self->{Consumer} = XML::SAX::Writer::HandleConsumer->new($self->{Output});
110    }
111    elsif (not $ref) {
112        $self->{Consumer} = XML::SAX::Writer::FileConsumer->new($self->{Output});
113    }
114    elsif (UNIVERSAL::can($self->{Output}, 'output')) {
115        $self->{Consumer} = $self->{Output};
116    }
117    else {
118        XML::SAX::Writer::Exception->throw( Message => 'Unknown option for Output' );
119    }
120    return $self;
121}
122#-------------------------------------------------------------------#
123
124#-------------------------------------------------------------------#
125# setEscaperRegex
126#-------------------------------------------------------------------#
127sub setEscaperRegex {
128    my $self = shift;
129
130    $self->{EscaperRegex} = eval 'qr/'                                                .
131                            join( '|', map { $_ = "\Q$_\E" } keys %{$self->{Escape}}) .
132                            '/;'                                                  ;
133    return $self;
134}
135#-------------------------------------------------------------------#
136
137#-------------------------------------------------------------------#
138# setAttributeEscaperRegex
139#-------------------------------------------------------------------#
140sub setAttributeEscaperRegex {
141    my $self = shift;
142
143        $self->{AttributeEscaperRegex} =
144            eval 'qr/'                                                         .
145            join( '|', map { $_ = "\Q$_\E" } keys %{$self->{AttributeEscape}}) .
146            '/;'                                                               ;
147    return $self;
148}
149#-------------------------------------------------------------------#
150
151#-------------------------------------------------------------------#
152# setCommentEscaperRegex
153#-------------------------------------------------------------------#
154sub setCommentEscaperRegex {
155    my $self = shift;
156
157    $self->{CommentEscaperRegex} =
158                        eval 'qr/'                                                .
159                        join( '|', map { $_ = "\Q$_\E" } keys %{$self->{CommentEscape}}) .
160                        '/;'                                                  ;
161    return $self;
162}
163#-------------------------------------------------------------------#
164
165#-------------------------------------------------------------------#
166# escape
167#-------------------------------------------------------------------#
168sub escape {
169    my $self = shift;
170    my $str  = shift;
171
172    $str =~ s/($self->{EscaperRegex})/$self->{Escape}->{$1}/ge;
173    return $str;
174}
175#-------------------------------------------------------------------#
176
177#-------------------------------------------------------------------#
178# escapeAttribute
179#-------------------------------------------------------------------#
180sub escapeAttribute {
181    my $self = shift;
182    my $str  = shift;
183
184    $str =~ s/($self->{AttributeEscaperRegex})/$self->{AttributeEscape}->{$1}/ge;
185    return $str;
186}
187#-------------------------------------------------------------------#
188
189#-------------------------------------------------------------------#
190# escapeComment
191#-------------------------------------------------------------------#
192sub escapeComment {
193    my $self = shift;
194    my $str  = shift;
195
196    $str =~ s/($self->{CommentEscaperRegex})/$self->{CommentEscape}->{$1}/ge;
197    return $str;
198}
199#-------------------------------------------------------------------#
200
201#-------------------------------------------------------------------#
202# convert and checking the return value
203#-------------------------------------------------------------------#
204sub safeConvert {
205    my $self = shift;
206    my $str = shift;
207
208    my $out = $self->{Encoder}->convert($str);
209
210    if (!defined $out && defined $str) {
211        warn "Conversion error returned by Encoder [$self->{Encoder}], string: '$str'";
212        $out = '_LOST_DATA_';
213    }
214    return $out;
215}
216#-------------------------------------------------------------------#
217
218
219#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
220#`,`, The Empty Consumer ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
221#```````````````````````````````````````````````````````````````````#
222
223# this package is only there to provide a smooth upgrade path in case
224# new methods are added to the interface
225
226package XML::SAX::Writer::ConsumerInterface;
227$XML::SAX::Writer::ConsumerInterface::VERSION = '0.57';
228sub new {
229    my $class = shift;
230    my $ref = shift;
231    ## $self is a reference to the reference that we will send output
232    ## to.  This allows us to bless $self without blessing $$self.
233    return bless \$ref, ref $class || $class;
234}
235
236sub output {}
237sub finalize {}
238
239
240#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
241#`,`, The String Consumer `,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
242#```````````````````````````````````````````````````````````````````#
243
244package XML::SAX::Writer::StringConsumer;
245$XML::SAX::Writer::StringConsumer::VERSION = '0.57';
246@XML::SAX::Writer::StringConsumer::ISA = qw(XML::SAX::Writer::ConsumerInterface);
247
248#-------------------------------------------------------------------#
249# new
250#-------------------------------------------------------------------#
251sub new {
252    my $self = shift->SUPER::new( @_ );
253    ${${$self}} = '';
254    return $self;
255}
256#-------------------------------------------------------------------#
257
258#-------------------------------------------------------------------#
259# output
260#-------------------------------------------------------------------#
261sub output { ${${$_[0]}} .= $_[1] }
262#-------------------------------------------------------------------#
263
264#-------------------------------------------------------------------#
265# finalize
266#-------------------------------------------------------------------#
267sub finalize { ${$_[0]} }
268#-------------------------------------------------------------------#
269
270#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
271#`,`, The Code Consumer `,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
272#```````````````````````````````````````````````````````````````````#
273
274package XML::SAX::Writer::CodeConsumer;
275$XML::SAX::Writer::CodeConsumer::VERSION = '0.57';
276@XML::SAX::Writer::CodeConsumer::ISA = qw(XML::SAX::Writer::ConsumerInterface );
277
278#-------------------------------------------------------------------#
279# new
280#-------------------------------------------------------------------#
281sub new {
282    my $self = shift->SUPER::new( @_ );
283    $$self->( 'start_document', '' );
284    return $self;
285}
286#-------------------------------------------------------------------#
287
288#-------------------------------------------------------------------#
289# output
290#-------------------------------------------------------------------#
291sub output { ${$_[0]}->('data', pop) } ## Avoid an extra copy
292#-------------------------------------------------------------------#
293
294#-------------------------------------------------------------------#
295# finalize
296#-------------------------------------------------------------------#
297sub finalize { ${$_[0]}->('end_document', '') }
298#-------------------------------------------------------------------#
299
300
301#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
302#`,`, The Array Consumer ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
303#```````````````````````````````````````````````````````````````````#
304
305package XML::SAX::Writer::ArrayConsumer;
306$XML::SAX::Writer::ArrayConsumer::VERSION = '0.57';
307@XML::SAX::Writer::ArrayConsumer::ISA = qw(XML::SAX::Writer::ConsumerInterface);
308
309#-------------------------------------------------------------------#
310# new
311#-------------------------------------------------------------------#
312sub new {
313    my $self = shift->SUPER::new( @_ );
314    @$$self = ();
315    return $self;
316}
317#-------------------------------------------------------------------#
318
319#-------------------------------------------------------------------#
320# output
321#-------------------------------------------------------------------#
322sub output { push @${$_[0]}, pop }
323#-------------------------------------------------------------------#
324
325#-------------------------------------------------------------------#
326# finalize
327#-------------------------------------------------------------------#
328sub finalize { return ${$_[0]} }
329#-------------------------------------------------------------------#
330
331
332#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
333#`,`, The Handle Consumer `,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
334#```````````````````````````````````````````````````````````````````#
335
336package XML::SAX::Writer::HandleConsumer;
337$XML::SAX::Writer::HandleConsumer::VERSION = '0.57';
338@XML::SAX::Writer::HandleConsumer::ISA = qw(XML::SAX::Writer::ConsumerInterface);
339
340#-------------------------------------------------------------------#
341# output
342#-------------------------------------------------------------------#
343sub output {
344    my $fh = ${$_[0]};
345    print $fh pop or XML::SAX::Exception->throw(
346        Message => "Could not write to handle: $fh ($!)"
347    );
348}
349#-------------------------------------------------------------------#
350
351#-------------------------------------------------------------------#
352# finalize
353#-------------------------------------------------------------------#
354sub finalize { return 0 }
355#-------------------------------------------------------------------#
356
357
358#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
359#`,`, The File Consumer `,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
360#```````````````````````````````````````````````````````````````````#
361
362package XML::SAX::Writer::FileConsumer;
363$XML::SAX::Writer::FileConsumer::VERSION = '0.57';
364@XML::SAX::Writer::FileConsumer::ISA = qw(XML::SAX::Writer::HandleConsumer);
365
366#-------------------------------------------------------------------#
367# new
368#-------------------------------------------------------------------#
369sub new {
370    my ( $proto, $file, $opt ) = @_;
371    my $enc_to = (defined $opt and ref $opt eq 'HASH'
372                  and defined $opt->{EncodeTo}) ? $opt->{EncodeTo}
373                                                : 'utf-8';
374
375    XML::SAX::Writer::Exception->throw(
376        Message => "No filename provided to " . ref( $proto || $proto )
377    ) unless defined $file;
378
379    local *XFH;
380
381    open XFH, ">:encoding($enc_to)", $file
382      or XML::SAX::Writer::Exception->throw(
383        Message => "Error opening file $file: $!"
384      );
385
386    return $proto->SUPER::new( *{XFH}{IO}, @_ );
387}
388#-------------------------------------------------------------------#
389
390#-------------------------------------------------------------------#
391# finalize
392#-------------------------------------------------------------------#
393sub finalize {
394    close ${$_[0]};
395    return 0;
396}
397#-------------------------------------------------------------------#
398
399
400#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
401#`,`, Noop Converter ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
402#```````````````````````````````````````````````````````````````````#
403
404package XML::SAX::Writer::NullConverter;
405$XML::SAX::Writer::NullConverter::VERSION = '0.57';
406sub new     { return bless [], __PACKAGE__ }
407sub convert { $_[1] }
408
409
410#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
411#`,`, Encode Converter ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
412#```````````````````````````````````````````````````````````````````#
413
414package XML::SAX::Writer::Encode;
415$XML::SAX::Writer::Encode::VERSION = '0.57';
416sub new {
417    my ($class, $from, $to) = @_;
418    my $self = {
419        from_enc => $from,
420        to_enc   => $to,
421    };
422    return bless $self, $class;
423}
424sub convert {
425    my ($self, $data) = @_;
426    eval {
427        $data = Encode::decode($self->{from_enc}, $data) if $self->{from_enc};
428        $data = Encode::encode($self->{to_enc}, $data, Encode::FB_CROAK) if $self->{to_enc};
429    };
430    if ($@) {
431        warn $@;
432        return;
433    }
434    return $data;
435};
436
437
4381;
439
440=pod
441
442=encoding UTF-8
443
444=head1 NAME
445
446XML::SAX::Writer - SAX2 XML Writer
447
448=head1 VERSION
449
450version 0.57
451
452=head1 SYNOPSIS
453
454  use XML::SAX::Writer;
455  use XML::SAX::SomeDriver;
456
457  my $w = XML::SAX::Writer->new;
458  my $d = XML::SAX::SomeDriver->new(Handler => $w);
459
460  $d->parse('some options...');
461
462=head1 DESCRIPTION
463
464=head2 Why yet another XML Writer ?
465
466A new XML Writer was needed to match the SAX2 effort because quite
467naturally no existing writer understood SAX2. My first intention had
468been to start patching XML::Handler::YAWriter as it had previously
469been my favourite writer in the SAX1 world.
470
471However the more I patched it the more I realised that what I thought
472was going to be a simple patch (mostly adding a few event handlers and
473changing the attribute syntax) was turning out to be a rewrite due to
474various ideas I'd been collecting along the way. Besides, I couldn't
475find a way to elegantly make it work with SAX2 without breaking the
476SAX1 compatibility which people are probably still using. There are of
477course ways to do that, but most require user interaction which is
478something I wanted to avoid.
479
480So in the end there was a new writer. I think it's in fact better this
481way as it helps keep SAX1 and SAX2 separated.
482
483=head1 METHODS
484
485=over 4
486
487=item * new(%hash)
488
489This is the constructor for this object. It takes a number of
490parameters, all of which are optional.
491
492=item * Output
493
494This parameter can be one of several things. If it is a simple
495scalar, it is interpreted as a filename which will be opened for
496writing. If it is a scalar reference, output will be appended to this
497scalar. If it is an array reference, output will be pushed onto this
498array as it is generated. If it is a filehandle, then output will be
499sent to this filehandle.
500
501Finally, it is possible to pass an object for this parameter, in which
502case it is assumed to be an object that implements the consumer
503interface L<described later in the documentation|/THE CONSUMER
504INTERFACE>.
505
506If this parameter is not provided, then output is sent to STDOUT.
507
508Note that there is no means to set an encoding layer on filehandles
509created by this module; if this is necessary, the calling code should
510first open a filehandle with the appropriate encoding set, and pass
511that filehandle to this module.
512
513=item * Escape
514
515This should be a hash reference where the keys are characters
516sequences that should be escaped and the values are the escaped form
517of the sequence. By default, this module will escape the ampersand
518(&), less than (<), greater than (>), double quote ("), and apostrophe
519('). Note that some browsers don't support the &apos; escape used for
520apostrophes so that you should be careful when outputting XHTML.
521
522If you only want to add entries to the Escape hash, you can first
523copy the contents of %XML::SAX::Writer::DEFAULT_ESCAPE.
524
525=item * CommentEscape
526
527Comment content often needs to be escaped differently from other
528content. This option works exactly as the previous one except that
529by default it only escapes the double dash (--) and that the contents
530can be copied from %XML::SAX::Writer::COMMENT_ESCAPE.
531
532=item * EncodeFrom
533
534The character set encoding in which incoming data will be provided.
535This defaults to UTF-8, which works for US-ASCII as well.
536
537Set this to C<undef> if you do not wish to decode your data.
538
539=item * EncodeTo
540
541The character set encoding in which output should be encoded. Again,
542this defaults to UTF-8.
543
544Set this to C<undef> if you do not with to encode your data.
545
546=item * QuoteCharacter
547
548Set the character used to quote attributes. This defaults to single quotes (')
549for backwards compatibility.
550
551=back
552
553=head1 THE CONSUMER INTERFACE
554
555XML::SAX::Writer can receive pluggable consumer objects that will be
556in charge of writing out what is formatted by this module. Setting a
557Consumer is done by setting the Output option to the object of your
558choice instead of to an array, scalar, or file handle as is more
559commonly done (internally those in fact map to Consumer classes and
560and simply available as options for your convenience).
561
562If you don't understand this, don't worry. You don't need it most of
563the time.
564
565That object can be from any class, but must have two methods in its
566API. It is also strongly recommended that it inherits from
567XML::SAX::Writer::ConsumerInterface so that it will not break if that
568interface evolves over time. There are examples at the end of
569XML::SAX::Writer's code.
570
571The two methods that it needs to implement are:
572
573=over 4
574
575=item * output STRING
576
577(Required)
578
579This is called whenever the Writer wants to output a string formatted
580in XML. Encoding conversion, character escaping, and formatting have
581already taken place. It's up to the consumer to do whatever it wants
582with the string.
583
584=item * finalize()
585
586(Optional)
587
588This is called once the document has been output in its entirety,
589during the end_document event. end_document will in fact return
590whatever finalize() returns, and that in turn should be returned
591by parse() for whatever parser was invoked. It might be useful if
592you need to provide feedback of some sort.
593
594=back
595
596Here's an example of a custom consumer.  Note the extra C<$> signs in
597front of $self; the base class is optimized for the overwhelmingly
598common case where only one data member is required and $self is a
599reference to that data member.
600
601    package MyConsumer;
602
603    @ISA = qw( XML::SAX::Writer::ConsumerInterface );
604
605    use strict;
606
607    sub new {
608        my $self = shift->SUPER::new( my $output );
609
610        $$self = '';      # Note the extra '$'
611
612        return $self;
613    }
614
615    sub output {
616        my $self = shift;
617        $$self .= uc shift;
618    }
619
620    sub get_output {
621        my $self = shift;
622        return $$self;
623    }
624
625And here is one way to use it:
626
627    my $c = MyConsumer->new;
628    my $w = XML::SAX::Writer->new( Output => $c );
629
630    ## ... send events to $w ...
631
632    print $c->get_output;
633
634If you need to store more that one data member, pass in an array or hash
635reference:
636
637        my $self = shift->SUPER::new( {} );
638
639and access it like:
640
641    sub output {
642        my $self = shift;
643        $$self->{Output} .= uc shift;
644    }
645
646=head1 THE ENCODER INTERFACE
647
648Encoders can be plugged in to allow one to use one's favourite encoder
649object. Presently there are two encoders: Encode and NullEncoder. They
650need to implement two methods, and may inherit from
651XML::SAX::Writer::NullConverter if they wish to
652
653=over 4
654
655=item new FROM_ENCODING, TO_ENCODING
656
657Creates a new Encoder. The arguments are the chosen encodings.
658
659=item convert STRING
660
661Converts that string and returns it.
662
663=back
664
665Note that the return value of the convert method is B<not> checked. Output may
666be truncated if a character couldn't be converted correctly. To avoid problems
667the encoder should take care encoding errors itself, for example by raising an
668exception.
669
670=head1 CUSTOM OUTPUT
671
672This module is generally used to write XML -- which it does most of the
673time -- but just like the rest of SAX it can be used as a generic
674framework to output data, the opposite of a non-XML SAX parser.
675
676Of course there's only so much that one can abstract, so depending on
677your format this may or may not be useful. If it is, you'll need to
678know the following API (and probably to have a look inside
679C<XML::SAX::Writer::XML>, the default Writer).
680
681=over
682
683=item init
684
685Called before the writing starts, it's a chance for the subclass to do
686some initialisation if it needs it.
687
688=item setConverter
689
690This is used to set the proper converter for character encodings. The
691default implementation should suffice but you can override it. It must
692set C<< $self->{Encoder} >> to an Encoder object. Subclasses *should* call
693it.
694
695=item setConsumer
696
697Same as above, except that it is for the Consumer object, and that it
698must set C<< $self->{Consumer} >>.
699
700=item setEscaperRegex
701
702Will initialise the escaping regex C<< $self->{EscaperRegex} >> based on
703what is needed.
704
705=item escape STRING
706
707Takes a string and escapes it properly.
708
709=item setCommentEscaperRegex and escapeComment STRING
710
711These work exactly the same as the two above, except that they are meant
712to operate on comment contents, which often have different escaping rules
713than those that apply to regular content.
714
715=back
716
717=head1 TODO
718
719    - proper UTF-16 handling
720
721    - the formatting options need to be developed.
722
723    - test, test, test (and then some tests)
724
725    - doc, doc, doc (actually this part is in better shape)
726
727    - remove the xml_decl and replace it with intelligent logic, as
728    discussed on perl-xml
729
730    - make a the Consumer selecting code available in the API, to avoid
731    duplicating
732
733    - add an Apache output Consumer, triggered by passing $r as Output
734
735=head1 CREDITS
736
737Michael Koehne (XML::Handler::YAWriter) for much inspiration and Barrie
738Slaymaker for the Consumer pattern idea, the coderef output option and
739miscellaneous bugfixes and performance tweaks. Of course the usual
740suspects (Kip Hampton and Matt Sergeant) helped in the usual ways.
741
742=head1 SEE ALSO
743
744XML::SAX::*
745
746=head1 AUTHORS
747
748=over 4
749
750=item *
751
752Robin Berjon <robin@knowscape.com>
753
754=item *
755
756Chris Prather <chris@prather.org>
757
758=back
759
760=head1 COPYRIGHT AND LICENSE
761
762This software is copyright (c) 2014 by Robin Berjon.
763
764This is free software; you can redistribute it and/or modify it under
765the same terms as the Perl 5 programming language system itself.
766
767=cut
768
769__END__
770#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
771#`,`, Documentation `,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,#
772#```````````````````````````````````````````````````````````````````#
773
774
775