package XML::Genx::SAXWriter; use strict; use warnings; use Carp (); use XML::Genx::Simple; use base 'XML::SAX::Base'; our $VERSION = '0.22'; sub new { my $class = shift; my $self = bless {}, $class; $self->_init( @_ ); return $self; } sub _init { my $self = shift; my %opt = @_; $self->_out( $opt{ out } || \*STDOUT ); return; } sub start_document { my $self = shift; $self->_w( XML::Genx::Simple->new ); if ( ref $self->_out eq 'SCALAR' ) { $self->_w->StartDocString; } elsif ( ref $self->_out eq 'GLOB' ) { $self->_w->StartDocFile( $self->_out ); } elsif ( ref $self->_out eq 'CODE' ) { $self->_w->StartDocSender( $self->_out ); } elsif ( ref $self->_out && $self->_out->isa( 'IO::Handle' ) ) { $self->_w->StartDocFile( $self->_out ); } elsif ( defined $self->_out && length $self->_out ) { open( my $fh, '>', $self->_out ) or Carp::croak( "open(".$self->_out."): $!" ); $self->_w->StartDocFile( $fh ); } else { Carp::croak( "start_document: no output specified!" ); } } sub end_document { my $self = shift; my $rv = $self->_w->EndDocument; if ( ref $self->_out eq 'SCALAR' ) { ${ $self->_out } = $self->_w->GetDocString; } return $rv; } sub start_element { my $self = shift; my ( $data ) = @_; my $ns = $self->_new_namespace( $data->{ NamespaceURI }, $data->{ Prefix } ); $self->_new_element( $ns, $data->{ LocalName } )->StartElement; while ( my $ns = $self->_pop_ns ) { # In order to get default namespaces done correctly, we have to # specify the prefix explicitly to AddNamespace(). See comments # above _declare_namespace(). my ( $uri, $prefix ) = @$ns; $self->_new_namespace( $uri, $prefix )->AddNamespace( $prefix ); } foreach ( values %{ $data->{ Attributes } || {} } ) { my $ns = $self->_new_namespace( $_->{ NamespaceURI }, $_->{ Prefix } ); $self->_new_attribute( $ns, $_->{ LocalName } ) ->AddAttribute( $_->{ Value } ); } return; } sub characters { my $self = shift; my ( $data ) = @_; $self->_w->AddText( $data->{ Data } ); } sub end_element { my $self = shift; my ( $data ) = @_; $self->_w->EndElement; } sub start_prefix_mapping { my $self = shift; my ( $data ) = @_; $self->_push_ns( $data->{ NamespaceURI }, $data->{ Prefix } ); } sub end_prefix_mapping { my $self = shift; my ( $data ) = @_; # XXX Do we need to do anything here? I don't think so. } sub processing_instruction { my $self = shift; my ( $data ) = @_; $self->_w->PI( $data->{ Target }, $data->{ Data } ); } sub comment { my $self = shift; my ( $data ) = @_; $self->_w->Comment( $data->{ Data } ); } #--------------------------------------------------------------------- # PRIVATE #--------------------------------------------------------------------- sub _w { my $self = shift; if ( @_ ) { $self->{ _w } = $_[0]; return $self; } else { return $self->{ _w }; } } sub _out { my $self = shift; if ( @_ ) { $self->{ _out } = $_[0]; return $self; } else { return $self->{ _out }; } } sub _push_ns { my $self = shift; my ( $ns, $prefix ) = @_; push @{ $self->{ nstodo } }, [$ns, $prefix]; return; } sub _pop_ns { my $self = shift; return pop @{ $self->{ nstodo } }; } # Return a declared namespace object if it's present. If no namespace # is given, return undef. sub _new_namespace { my $self = shift; my ( $nsuri, $prefix ) = @_; return unless $nsuri; return $self->{ namespace }{ $nsuri } ||= $self->_declare_namespace( $nsuri, $prefix ); } # Get Genx to give us a new namespace object. NB: If a default # namespace is requested, we ask Genx to assign an autogenerated prefix # instead. This gets countered further up in start_element(). When we # call AddNamespace(), we pass in the requested prefix instead, which # will be "" in case of the default namespace. This is all in order to # work around Genx, which will not let you declare two namespaces with # the default prefix. sub _declare_namespace { my $self = shift; my ( $nsuri, $prefix ) = @_; if ( $prefix ) { return $self->_w->DeclareNamespace( $nsuri, $prefix ); } else { return $self->_w->DeclareNamespace( $nsuri ); } } sub _new_element { my $self = shift; my ( $ns, $lname ) = @_; return $self->{ element }{ $lname } ||= $self->_w->DeclareElement( $ns, $lname ); } sub _new_attribute { my $self = shift; my ( $ns, $lname ) = @_; return $self->{ attribute }{ $lname } ||= $self->_w->DeclareAttribute( $ns, $lname ); } 1; __END__ =pod =head1 NAME XML::Genx::SAXWriter - output a SAX stream using genx =head1 SYNOPSIS # Copy input to output. my $w = XML::Genx::SAXWriter->new; my $p = XML::SAX::ParserFactory->parser( Handler => $w ); $p->parse_file( *STDIN ); =head1 DESCRIPTION This class provides a means of writing output from a stream of SAX events. See L and L for more details on what SAX is. Essentially, this is just a wrapper over L, mapping calls from SAX to genx. =head1 METHODS The following methods are defined as part of the usual L interface. =over 4 =item new() =item characters() =item comment() =item end_document() =item end_element() =item end_prefix_mapping() =item processing_instruction() =item start_document() =item start_element() =item start_prefix_mapping() =back =head1 VERSION @(#) $Id: SAXWriter.pm 1270 2006-10-08 17:29:33Z dom $ =cut