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 '<' => '<', 19 '>' => '>', 20 '"' => '"', 21 "'" => ''', 22 ); 23 24%ATTRIBUTE_ESCAPE = ( 25 %DEFAULT_ESCAPE, 26 "\t" => '	', 27 "\n" => '
', 28 "\r" => '
', 29 ); 30 31%COMMENT_ESCAPE = ( 32 '--' => '--', 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 ' 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