PITA::XML::SAXDriver - Implements a SAX Driver for PITA::XML objects


PITA-XML documentation Contained in the PITA-XML distribution.

Index


Code Index:

NAME

Top

PITA::XML::SAXDriver - Implements a SAX Driver for PITA::XML objects

DESCRIPTION

Top

Although you won't need to use it directly, this class provides a "SAX Driver" class that converts a PITA::XML object into a stream of SAX events (which will mostly likely be written to a file).

Please note that this class is incomplete at this time. Although you can create objects, you can't actually run them yet.

METHODS

Top

new

  # Create a SAX Driver to generate in-memory
  $driver = PITA::XML::SAXDriver->new();

  # ... or to stream (write) to a file
  $driver = PITA::XML::SAXDriver->new( Output => 'filename' );

  # ... or to send the events to a custom handler
  $driver = PITA::XML::SAXDriver->new( Handler => $handler   );

The new constructor creates a new SAX generator for PITA-XML files.

It takes a named param of EITHER an XML Handler object, or an Output value that is compatible with XML::SAX::Writer.

Returns a PITA::XML::SAXDriver object, or dies on error.

NamespaceURI

The NamespaceURI returns the name of the XML namespace being used in the file generation.

While PITA is still in development, this should be something like the following, where $VERSION is the PITA::XML version string.

  http://ali.as/xml/schema/pita-xml/$VERSION

Prefix

The Prefix returns the name of the XML prefix being used for the output.

Handler

The Handler returns the SAX Handler object that the SAX events are being sent to. This will be or the SAX Handler object you originally passed in, or a XML::SAX::Writer object pointing at your Output value.

Output

If you did not provide a custom SAX Handler, the Output accessor returns the location you are writing the XML output to.

If you did not provide a Handler or Output param to the constructor, then this returns a SCALAR reference containing the XML as a string.

SUPPORT

Top

Bugs should be reported via the CPAN bug tracker at

http://rt.cpan.org/NoAuth/ReportBug.html?Queue=PITA-XML

For other issues, contact the author.

AUTHOR

Top

Adam Kennedy <adamk@cpan.org>, http://ali.as/

SEE ALSO

Top

PITA::XML, PITA::XML::SAXParser

The Perl Image-based Testing Architecture (http://ali.as/pita/)

COPYRIGHT

Top


PITA-XML documentation Contained in the PITA-XML distribution.
package PITA::XML::SAXDriver;

use 5.006;
use strict;
use Carp           ();
use Params::Util   ':ALL';
use Class::Autouse 'XML::SAX::Writer';
use PITA::XML      ();
use XML::SAX::Base ();

use vars qw{$VERSION @ISA};
BEGIN {
	$VERSION = '0.51';
	@ISA     = 'XML::SAX::Base';
}





#####################################################################
# Constructor

sub new {
	my $class = shift;
	my $self  = bless {
		NamespaceURI => PITA::XML->XMLNS,
		Prefix       => '',
		@_,
	}, $class;

	# Add a default SAX Handler
	unless ( $self->{Handler} ) {
		# We are going to create a file writer to anything
		# that it supports. So we will need an Output param.
		unless ( $self->{Output} ) {
			my $Output = '';
			$self->{Output} = \$Output;
		}

		# Create the file writer
		$self->{Handler} = XML::SAX::Writer->new(
			Output => $self->{Output},
		) or Carp::croak("Failed to create XML Writer for Output");
	}

	# Check the namespace
	unless ( _STRING($self->{NamespaceURI}) ) {
		Carp::croak("Invalid NamespaceURI");
	}

	# Flag that an xmlns attribute be added
	# to the first element in the document
	$self->{xmlns} = $self->{NamespaceURI};

	$self;
}

sub NamespaceURI {
	$_[0]->{NamespaceURI};
}

sub Prefix {
	$_[0]->{Prefix};
}

sub Handler {
	$_[0]->{Handler};
}

sub Output {
	$_[0]->{Output};
}





#####################################################################
# Main SAX Methods

# Prevent use as a SAX Filter or SAX Parser
# We only generate SAX events, we don't consume them.
#sub start_document {
#	my $class = ref $_[0] || $_[0];
#	die "$class is not a SAX Filter or Driver, it cannot recieve events";
#}

sub parse {
	my $self = shift;
	my $root = _INSTANCE(shift, 'PITA::XML::Storable');
	unless ( $root ) {
		Carp::croak("Did not provide a writable root object");
	}

	# Attach the xmlns to the first tag
	if ( $self->{NamespaceURI} ) {
		$self->{xmlns} = $self->{NamespaceURI};
	}

	# Generate the SAX2 events
	$self->start_document( {} );
	if ( _INSTANCE($root, 'PITA::XML::Report') ) {
		$self->_parse_report( $root );
	} elsif ( _INSTANCE($root, 'PITA::XML::Request') ) {
		$self->_parse_request( $root );
	} elsif ( _INSTANCE($root, 'PITA::XML::Guest') ) {
		$self->_parse_guest( $root );
	} else {
		die("Support for " . ref($root) . " not implemented");
	}
	$self->end_document( {} );

	return 1;
}

sub start_document {
	my $self = shift;

	# Do the normal start_document tasks
	$self->SUPER::start_document( @_ );

	# And always put the XML declaration at the start
	$self->xml_decl( {
		Version  => '1.0',
		Encoding => 'UTF-8',
	} );

	return 1;
}

# Generate events for the parent PITA::XML::Report object
sub _parse_report {
	my $self   = shift;
	my $report = shift;

	# Send the open tag
	my $element = $self->_element( 'report' );
	$self->start_element( $element );

	# Iterate over the individual installations
	foreach my $install ( $report->installs ) {
		$self->_parse_install( $install );
	}

	# Send the close tag
	$self->end_element($element);

	return 1;
}

# Generate events for a single install
sub _parse_install {
	my $self    = shift;
	my $install = shift;

	# Send the open tag
	my $element = $self->_element( 'install' );
	$self->start_element( $element );

	# Send the optional configuration tag
	$self->_parse_request( $install->request );

	# Send the optional platform tag
	$self->_parse_platform( $install->platform );

	# Add the command tags
	foreach my $command ( $install->commands ) {
		$self->_parse_command( $command );
	}

	# Add the test tags
	foreach my $test ( $install->tests ) {
		$self->_parse_test( $test );
	}

	# Add the optional analysis tag
	my $analysis = $install->analysis;
	if ( $analysis ) {
		$self->_parse_analysis( $analysis );
	}

	# Send the close tag
	$self->end_element( $element );

	return 1;
}

# Generate events for a request
sub _parse_request {
	my $self    = shift;
	my $request = shift;

	# Send the open tag
	my $attr = $request->id
		? { id => $request->id }
		: { };
	my $element = $self->_element( 'request', $attr );
	$self->start_element( $element );

	# Send the main accessors
	$self->_accessor_element( $request, 'scheme'   );
	$self->_accessor_element( $request, 'distname' );

	# Send the file(s)
	$self->_parse_file( $request->file );

	# Send the optional authority information
	if ( $request->authority ) {
		$self->_accessor_element( $request, 'authority' );
		if ( $request->authpath ) {
			$self->_accessor_element( $request, 'authpath' );
		}
	}

	# Send the close tag
	$self->end_element( $element );

	return 1;
}

# Generate events for a guest
sub _parse_guest {
	my $self  = shift;
	my $guest = shift;

	# Send the open tag
	my $attr = $guest->id
		? { id => $guest->id }
		: { };
	my $element = $self->_element( 'guest', $attr );
	$self->start_element( $element );

	# Send the main accessors
	$self->_accessor_element( $guest, 'driver' );

	# Iterate over the individual files
	foreach my $file ( $guest->files ) {
		$self->_parse_file( $file );
	}

	# Send each of the config variables
	my $config = $guest->config;
	foreach my $name ( sort keys %$config ) {
		my $el = $self->_element( 'config', { name => $name } );
		$self->start_element( $el );
		defined($config->{$name})
			? $self->characters( $config->{$name} )
			: $self->_undef;
		$self->end_element( $el );
	}

	# Iterate over the individual platforms
	foreach my $platform ( $guest->platforms ) {
		$self->_parse_platform( $platform );
	}

	# Send the close tag
	$self->end_element($element);

	return 1;
}

# Generate events for a file
sub _parse_file {
	my $self = shift;
	my $file = shift;

	# Send the open tag
	my $element = $self->_element( 'file' );
	$self->start_element( $element );

	# Send the main accessors
	$self->_accessor_element( $file, 'filename' );

	# Send the optional resource name
	if ( defined $file->resource ) {
		my $el = $self->_element( 'resource' );
		$self->start_element( $el );
		$self->characters( $file->resource );
		$self->end_element( $el );
	}

	# Send the optional digest
	if ( defined $file->digest ) {
		my $el = $self->_element( 'digest' );
		$self->start_element( $el );
		$self->characters( $file->digest->as_string );
		$self->end_element( $el );
	}

	# Send the close tag
	$self->end_element( $element );

	return 1;
}

# Generate events for a platform configuration
sub _parse_platform {
	my $self     = shift;
	my $platform = shift;

	# Send the open tag
	my $element = $self->_element( 'platform' );
	$self->start_element( $element );

	# Send the scheme
	if ( $platform->scheme ) {
		my $el = $self->_element( 'scheme' );
		$self->start_element( $el );
		$self->characters( $platform->scheme );
		$self->end_element( $el );
	}

	# Send the path
	if ( $platform->path ) {
		my $el = $self->_element( 'path' );
		$self->start_element( $el );
		$self->characters( $platform->path );
		$self->end_element( $el );
	}

	# Send each of the environment variables
	my $env = $platform->env;
	foreach my $name ( sort keys %$env ) {
		my $el = $self->_element( 'env', { name => $name } );
		$self->start_element( $el );
		defined($env->{$name})
			? $self->characters( $env->{$name} )
			: $self->_undef;
		$self->end_element( $el );
	}

	# Send each of the config variables
	my $config = $platform->config;
	foreach my $name ( sort keys %$config ) {
		my $el = $self->_element( 'config', { name => $name } );
		$self->start_element( $el );
		defined($config->{$name})
			? $self->characters( $config->{$name} )
			: $self->_undef;
		$self->end_element( $el );
	}

	# Send the close tag
	$self->end_element( $element );

	return 1;
}

sub _parse_command {
	my $self    = shift;
	my $command = shift;

	# Send the open tag
	my $element = $self->_element( 'command' );
	$self->start_element( $element );

	# Send the accessors
	$self->_accessor_element( $command, 'cmd'    );
	$self->_accessor_element( $command, 'stdout' );
	$self->_accessor_element( $command, 'stderr' );

	# Send the close tag
	$self->end_element( $element );

	return 1;
}

sub _parse_test {
	my $self = shift;
	my $test = shift;

	# Send the open tag
	my $attrs = {
		language => $test->language,
	};
	if ( defined $test->name ) {
		$attrs->{name} = $test->name;
	}
	my $element = $self->_element( 'test', $attrs );
	$self->start_element( $element );

	# Send the accessor elements
	$self->_accessor_element( $test, 'stdout' );
	if ( defined $test->stderr ) {
		$self->_accessor_element( $test, 'stderr' );
	}
	if ( defined $test->exitcode ) {
		$self->_accessor_element( $test, 'exitcode' );
	}

	# Send the close tag
	$self->end_element( $element );

	return 1;
}

sub _parse_analysis {
	die "CODE INCOMPLETE";
}

# Specifically send an undef tag pair
sub _undef {
	my $self = shift;
	my $el   = $self->_element('null');
	$self->start_element( $el );
	$self->end_element( $el );
}





#####################################################################
# Support Methods

# Make sure the first element gets an xmlns attribute
sub start_element {
	my $self    = shift;
	my $element = shift;
	my $xmlns   = delete $self->{xmlns};

	# Shortcut for the most the common case
	unless ( $xmlns ) {
		return $self->SUPER::start_element( $element );
	}

	# Add the XMLNS Attribute
	$element->{Attributes}->{'xmlns'} = {
		Prefix    => '',
		LocalName => 'xmlns',
		Name      => 'xmlns',
		Value     => $xmlns,
	};

	# Pass on to the parent class
	$self->SUPER::start_element( $element );
}

# Strip out the Attributes for the end element
sub end_element {
	delete $_[1]->{Attributes};
	shift->SUPER::end_element(@_);
}

sub _element {
	my $self       = shift;
	my $LocalName  = shift;
	my $attrs      = _HASH(shift) || {};

	# Localise some variables for speed
	my $NamespaceURI = $self->{NamespaceURI};
	my $Prefix       = $self->{Prefix}
		? "$self->{Prefix}:"
		: '';

	# Convert the attributes to the full version
	my %Attributes = ();
	if ( $attrs->{xmlns} ) {
		# The xmlns attribute is always first
		my $value = delete $attrs->{xmlns};
		$Attributes{xmlns} = {
			Name         => 'xmlns',
			#NamespaceURI => $NamespaceURI,
			#Prefix       => $Prefix,
			#LocalName    => $key,
			Value        => $value,
		};
	}
	foreach my $key ( sort keys %$attrs ) {
		#$Attributes{"{$NamespaceURI}$key"} = {
		$Attributes{$key} = {
			Name         => $Prefix . $key,
			#NamespaceURI => $NamespaceURI,
			#Prefix       => $Prefix,
			#LocalName    => $key,
			Value        => $attrs->{$key},
		};
	}

	# Complete the main element
	return {
		Name         => $Prefix . $LocalName,
		#NamespaceURI => $NamespaceURI,
		#Prefix       => $Prefix,
		#LocalName    => $LocalName,
		Attributes   => \%Attributes,
	};
}

# Send a matching tag for a known object accessor
sub _accessor_element {
	my $self   = shift;
	my $object = shift;
	my $method = shift;
	my $value  = $object->$method();

	# Generate the element and send it
	my $el = $self->_element( $method );
	$self->start_element( $el );
	$self->characters( $value );
	$self->end_element( $el );
}

# Auto-preparation of the text
sub characters {
	my $self = shift;

	# A { Data => '...' } string
	if ( _HASH($_[0]) ) {
		return $self->SUPER::characters(shift);
	}

	# A normal string, by reference
	if ( _SCALAR0($_[0]) ) {
		my $scalar_ref = shift;
		return $self->SUPER::characters( {
			Data => $$scalar_ref,
		} );
	}

	# Must be a normal string
	$self->SUPER::characters( {
		Data => shift,
	} );
}

### Not sure if we escape here.
### Just pass through for now.
sub _escape { $_[1] }

1;