package tests::XMLParserTest;
use strict;
use base qw/Lire::Test::TestCase/;
use Lire::XMLParser;
use Lire::Test::Mock;
use Lire::Utils qw/tempdir create_file/;
use Class::Inner;
sub new {
my $self = shift->SUPER::new( @_ );
return $self;
}
sub set_up {
my $self = $_[0];
$self->SUPER::set_up();
$self->{'expat'} = new Lire::Test::Mock( 'XML::Parser::Expat' );
$self->{'expat'}->set_result( 'xpcroak' => sub { shift; die @_ },
'xpcarp' => sub { shift; warn @_ } );
return;
}
sub tear_down {
my $self = $_[0];
$self->SUPER::tear_down();
$self->{'expat'}->clean_symbol_table();
return;
}
sub test_new {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$self->assert_isa( 'Lire::XMLParser', $parser );
$self->assert_deep_equals( {}, $parser->{'_xml_collectors'} );
$self->assert_deep_equals( {}, $parser->{'_xml_stacks'} );
}
sub test_Init {
my $self = $_[0];
my $parser = new Lire::Test::Mock( 'Lire::XMLParser' );
$self->{'expat'}{'_LireXMLParser'} = $parser;
Lire::XMLParser::Init( $self->{'expat'} );
$self->assert_str_equals( $self->{'expat'}, $parser->{'_xml_expat'} );
$self->assert_deep_equals( [ '_build_ns_maps',
'_build_dtd',
'parse_start' ], $parser->get_calls() );
$self->assert_deep_equals( [ $parser ],
$parser->get_invocation( 'parse_start' ) );
}
sub test_Start {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$self->{'expat'}{'_LireXMLParser'} = $parser;
$parser->{'_xml_expat'} = $self->{'expat'};
$parser->{'_xml_ns2prefix'}{'mynamespace'} = 'lire';
$parser->{'_xml_elements'} = { 'lire:report' =>
{ 'content' => { 'lire:subreport' => 1 } },
'lire:subreport' =>
{ 'content' => {} },
'lire:other' =>
{ 'content' => {} },
};
$self->{'expat'}->set_result( 'namespace' => 'mynamespace',
'current_element' => undef );
$self->assert_dies( qr/encountered unknown element 'lire:unknown'/,
sub { Lire::XMLParser::Start( $self->{'expat'}, 'unknown' ) } );
Lire::XMLParser::Start( $self->{'expat'}, 'report' );
$self->assert_num_equals( 1, $parser->invocation_count( 'element_start' ));
$self->assert_deep_equals( [ $parser, 'lire:report', {} ],
$parser->get_invocation( 'element_start' ));
$self->{'expat'}->set_result( 'current_element' => 'report' );
$self->assert_dies( qr/'lire:other' element cannot appear in the context of 'lire:report'/,
sub { Lire::XMLParser::Start( $self->{'expat'}, 'other' ) } );
Lire::XMLParser::Start( $self->{'expat'}, 'subreport', 'attr' => 'value' );
$self->assert_num_equals( 2, $parser->invocation_count( 'element_start' ));
$self->assert_deep_equals( [ $parser, 'lire:subreport',
{ 'attr' => 'value' } ],
$parser->get_invocation( 'element_start' , 1 ));
}
sub test_End {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$self->{'expat'}{'_LireXMLParser'} = $parser;
$parser->{'_xml_expat'} = $self->{'expat'};
$parser->{'_xml_ns2prefix'}{'mynamespace'} = 'lire';
$self->{'expat'}->set_result( 'namespace' => 'mynamespace' );
Lire::XMLParser::End( $self->{'expat'}, 'report' );
$self->assert_num_equals( 1, $parser->invocation_count( 'element_end' ));
$self->assert_deep_equals( [ $parser, 'lire:report' ],
$parser->get_invocation( 'element_end' ));
}
sub test_Char {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$self->{'expat'}{'_LireXMLParser'} = $parser;
$parser->{'_xml_expat'} = $self->{'expat'};
$parser->{'_xml_elements'} = { 'caution' => { 'content' => { 'para' => 1}},
'para' => { 'content' => { 'PCDATA' => 1}}};
$self->{'expat'}->set_result( 'current_element' => 'para' );
Lire::XMLParser::Char( $self->{'expat'}, 'report' );
$self->assert_num_equals( 1, $parser->invocation_count( 'pcdata' ) );
$self->assert_deep_equals( [ $parser, 'report' ],
$parser->get_invocation( 'pcdata' ) );
$self->{'expat'}->set_result( 'current_element' => 'caution' );
$self->assert_dies( qr/non-white space character in element 'caution' which cannot contain PCDATA/,
sub { Lire::XMLParser::Char( $self->{'expat'}, 'a char' ) } );
Lire::XMLParser::Char( $self->{'expat'}, "\t\n " );
$self->assert_num_equals( 1, $parser->invocation_count( 'ignorable_ws' ) );
$self->assert_deep_equals( [ $parser, "\t\n " ],
$parser->get_invocation( 'ignorable_ws' ) );
}
sub test_Final {
my $self = $_[0];
my $parser = new Lire::Test::Mock( 'Lire::XMLParser',
'parse_end' => 'called' );
$parser->{'_xml_expat'} = 1;
my $result = Lire::XMLParser::Final( { '_LireXMLParser' => $parser } );
$self->assert_str_equals( 'called', $result );
$self->assert_deep_equals( [ 'parse_end' ], $parser->get_calls() );
$self->assert_deep_equals( [ $parser ],
$parser->get_invocation( 'parse_end' ) );
$self->assert_null( $parser->{'_xml_expat'}, "_xml_expat != undef" );
}
sub test__build_ns_maps {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'namespaces' => { 'http://mynamespace/' => 'lire',
'http://otherna/' => 'ns' } );
$parser->_build_ns_maps();
$self->assert_deep_equals( { 'lire' => 'http://mynamespace/',
'ns' => 'http://otherna/' },
$parser->{'_xml_prefix2ns'} );
$self->assert_deep_equals( { 'http://mynamespace/' => 'lire',
'http://otherna/' => 'ns' },
$parser->{'_xml_ns2prefix'} );
$parser->set_result( 'namespaces' => { 'http://mynamespace/' => '' } );
$self->assert_dies( qr/missing prefix/,
sub { $parser->_build_ns_maps() } );
$parser->set_result( 'namespaces' => { 'http://mynamespace/' => '0' } );
$self->assert_dies( qr/invalid prefix/,
sub { $parser->_build_ns_maps() } );
$parser->set_result( 'namespaces' => { 'http://mynamespace/' => 'lire',
'other_ns' => 'lire' } );
$self->assert_dies( qr/prefix \'lire\' already used/,
sub { $parser->_build_ns_maps() } );
}
sub test__build_dtd {
my $self = $_[0];
my $parser =
new Class::Inner( 'parent' => 'Lire::XMLParser',
'methods' =>
{ 'elements_spec' => sub { return $_[0]->{'spec'} },
'element_start' => sub {},
'element_char' => sub {},
'element_end' => sub {},
'a_method' => sub {},
'report_spec_start' => sub {},
} );
$parser->{'_xml_prefix2ns'} = { 'lire' => 'mynamespace' };
my $sub = sub {};
$parser->{'spec'} =
{ 'lire:report-spec' => { 'content' => [ 'lire:element', 'other' ] },
'lire:element' => [ 'PCDATA' ],
'other' => { 'start' => $sub,
'end' => 'a_method' },
};
$parser->{'_xml_expat'} = $self->{'expat'};
$self->{'expat'}->set_result( 'generate_ns_name' =>
sub { return "$_[2]/$_[1]" } );
$parser->_build_dtd();
$self->assert_deep_equals( { 'lire:report-spec' =>
{ 'content' => { 'lire:element' => 1,
'other' => 1 },
'start' => $parser->can( 'report_spec_start' ),
'end' => undef,
'char' => undef,
'expat_name' => "mynamespace/report-spec",
},
'lire:element' =>
{
'content' => { 'PCDATA' => 1 },
'start' => $parser->can( 'element_start' ),
'end' => $parser->can( 'element_end' ),
'char' => $parser->can( 'element_char' ),
'expat_name' => "mynamespace/element",
},
'other' =>
{
'content' => {},
'start' => $sub,
'char' => undef,
'end' => $parser->can( 'a_method' ),
'expat_name' => "/other",
},
},
$parser->{'_xml_elements'} );
$parser->{'spec'}{'other'}{'content'} = [ 'no_such_element' ];
$self->assert_dies( qr/element 'no_such_element' used in content of 'other' isn't defined by elements_spec/,
sub { $parser->_build_dtd() } );
delete $parser->{'spec'}{'other'}{'content'};
$parser->{'_xml_prefix2ns'} = {};
$self->assert_dies( qr/prefix 'lire' isn't defined by namespaces/,
sub { $parser->_build_dtd() } );
}
sub test__prefixed_name {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$parser->{'_xml_expat'} = $self->{'expat'};
$self->{'expat'}->set_result( 'namespace' => undef );
$self->assert_str_equals( 'name', $parser->_prefixed_name( 'name' ) );
$self->{'expat'}->set_result( 'namespace' => 'mynamespace' );
$parser->{'_xml_ns2prefix'} = { 'mynamespace' => 'lire' };
$self->assert_str_equals( 'lire:name', $parser->_prefixed_name( 'name' ) );
$self->{'expat'}->set_result( 'namespace' => 'otherns' );
$self->assert_dies( qr/namespace 'otherns' wasn't defined by namespaces/,
sub{ $parser->_prefixed_name( 'name' ) } );
}
sub test__find_handler {
my $self = $_[0];
my $parser =
new Class::Inner( 'parent' => 'Lire::XMLParser',
'methods' =>
{ 'test_start' => sub {},
'a_method' => sub {},
} );
my $sub = sub {};
$self->assert_str_equals( $sub,
$parser->_find_handler( $sub, 'test_start' ) );
$self->assert_str_equals( $parser->can( 'a_method' ),
$parser->_find_handler( 'a_method' ) );
$self->assert_dies( qr/no handler 'no_method' defined in/,
sub { $parser->_find_handler( 'no_method' ) } );
$self->assert_str_equals( $parser->can( 'test_start' ),
$parser->_find_handler( undef, 'test_start' ) );
}
sub test_in_element {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$parser->{'_xml_elements'}{'lire:report'}{'expat_name'} = 'myname';
$parser->{'_xml_expat'} = $self->{'expat'};
$self->assert_dies( qr/no element 'wawa' defined by elements_spec/,
sub { $parser->in_element( 'wawa' ) } );
$parser->in_element( 'lire:report' );
$self->assert_deep_equals( [ $self->{'expat'}, 'myname' ],
$self->{'expat'}->get_invocation( 'in_element' ) );
}
sub test_within_element {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$parser->{'_xml_elements'}{'lire:report'}{'expat_name'} = 'myname';
$parser->{'_xml_expat'} = $self->{'expat'};
$self->assert_dies( qr/no element 'wawa' defined by elements_spec/,
sub { $parser->within_element( 'wawa' ) } );
$parser->within_element( 'lire:report' );
$self->assert_deep_equals( [ $self->{'expat'}, 'myname' ],
$self->{'expat'}->get_invocation( 'within_element' ) );
}
sub test_element_start {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$parser->{'_xml_elements'}{'lire:report'}{'start'}
= sub { $_[0]->{'called'} = \@_ };
$parser->{'_xml_elements'}{'para'}{'start'} = undef;
$parser->element_start( 'para' ); # Should just work
my $attr = {};
$parser->element_start( 'lire:report', $attr );
$self->assert( exists $parser->{'called'}, "handler wasn't called" );
$self->assert_deep_equals( [ $parser, 'lire:report', $attr ],
$parser->{'called'} );
}
sub test_element_end {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$parser->{'_xml_elements'}{'lire:report'}{'end'}
= sub { $_[0]->{'called'} = \@_ };
$parser->{'_xml_elements'}{'para'}{'end'} = undef;
$parser->element_end( 'para' ); # Should just work
$parser->element_end( 'lire:report' );
$self->assert( exists $parser->{'called'}, "handler wasn't called" );
$self->assert_deep_equals( [ $parser, 'lire:report' ],
$parser->{'called'} );
}
sub test_pcdata {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$parser->{'_xml_expat'} = $self->{'expat'};
$parser->{'_xml_elements'}{'lire:report'}{'char'}
= sub { $_[0]->{'called'} = \@_ };
$parser->{'_xml_elements'}{'para'}{'char'} = undef;
$self->{'expat'}->set_result( 'current_element', 'para' );
$parser->pcdata( 'text' ); # Should just work
$self->{'expat'}->set_result( 'current_element', 'lire:report' );
$parser->pcdata( 'text' );
$self->assert( exists $parser->{'called'}, "handler wasn't called" );
$self->assert_deep_equals( [ $parser, 'text' ], $parser->{'called'} );
}
sub test_init_collector {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$parser->init_collector( 'mycollector' );
$self->assert_str_equals( '',
$parser->{'_xml_collectors'}{'mycollector'} );
}
sub test_collect {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'error' => sub { shift; die @_ } );
$self->assert_dies( qr/no collector 'unknown' defined/,
sub { $parser->collect( 'unknown', 'test' ) } );
$parser->{'_xml_collectors'}{'test'} = 'some_string';
$parser->collect( 'test', ' another string' );
$self->assert_str_equals( 'some_string another string',
$parser->{'_xml_collectors'}{ 'test' } );
}
sub test_get_collector {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'error' => sub { shift; die @_ } );
$self->assert_dies( qr/no collector 'unknown' defined/,
sub { $parser->get_collector( 'unknown' ) } );
$parser->{'_xml_collectors'}{'test'} = '';
$self->assert_str_equals( '', $parser->get_collector( 'test' ) );
}
sub test_collector_start {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->collector_start( 'lire:test' );
$self->assert_str_equals( '', $parser->get_collector( 'lire:test' ) );
}
sub test_collector_char {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'current_element' => 'lire:test' );
$parser->{'_xml_collectors'}{'lire:test'} = '';
$parser->collector_char( 'test' );
$self->assert_str_equals( 'test', $parser->get_collector( 'lire:test' ) );
}
sub test_init_stack {
my $self = $_[0];
my $parser = new Lire::XMLParser();
$parser->init_stack( 'stack' );
$self->assert_deep_equals( [],
$parser->{'_xml_stacks'}{'stack'} );
}
sub test_is_stack_empty {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'error' => sub { shift; die @_ } );
$self->assert_dies( qr/no stack 'test' defined/,
sub { $parser->is_stack_empty( 'test' ) } );
$parser->{'_xml_stacks'}{'test'} = [];
$self->assert( $parser->is_stack_empty( 'test' ), 'is_stack_empty()' );
$parser->{'_xml_stacks'}{'test'} = [ undef ];
$self->assert( !$parser->is_stack_empty( 'test' ), '!is_stack_empty()' );
}
sub test_stack_depth {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'error' => sub { shift; die @_ } );
$self->assert_dies( qr/no stack 'test' defined/,
sub { $parser->stack_depth( 'test' ) } );
$parser->{'_xml_stacks'}{'test'} = [];
$self->assert_num_equals( 0, $parser->stack_depth( 'test' ) );
$parser->{'_xml_stacks'}{'test'} = [ undef ];
$self->assert_num_equals( 1, $parser->stack_depth( 'test' ) );
}
sub test_stack_push {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'error' => sub { shift; die @_ } );
$self->assert_dies( qr/no stack 'unknown' defined/,
sub { $parser->stack_push( 'unknown', {} ) } );
$parser->{'_xml_stacks'}{'test'} = [];
$parser->stack_push( 'test', 'test' );
$self->assert_deep_equals( [ 'test' ], $parser->{'_xml_stacks'}{'test'} );
}
sub test_stack_pop {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'error' => sub { shift; die @_ } );
$self->assert_dies( qr/no stack 'test' defined/,
sub { $parser->stack_pop( 'test' ) } );
$parser->{'_xml_stacks'}{'test'} = [];
$self->assert_dies( qr/stack 'test' is empty/,
sub { $parser->stack_pop( 'test' ) } );
$parser->{'_xml_stacks'}{'test'} = [ 'test' ];
$self->assert_str_equals( 'test', $parser->stack_pop( 'test' ) );
$self->assert_deep_equals( [], $parser->{'_xml_stacks'}{'test'} );
}
sub test_stack_peek {
my $self = $_[0];
my $parser = new_proxy Lire::Test::Mock( 'Lire::XMLParser' );
$parser->set_result( 'error' => sub { shift; die @_ } );
$self->assert_dies( qr/no stack 'test' defined/,
sub { $parser->stack_peek( 'test' ) } );
$parser->{'_xml_stacks'}{'test'} = [];
$self->assert_dies( qr/stack 'test' is empty/,
sub { $parser->stack_peek( 'test' ) } );
$parser->{'_xml_stacks'}{'test'} = [ 'test' ];
$self->assert_str_equals( 'test', $parser->stack_peek( 'test' ) );
$self->assert_deep_equals( [ 'test' ], $parser->{'_xml_stacks'}{'test'} );
}
sub test_parse {
my $self = $_[0];
my $parser = tests::XMLParserTest::BasicParser->new();
my $result = $parser->parse( <<EOF );
<?xml version="1.0"?>
<lire:report xmlns:lire="http://www.logreport.org/">
<lire:subreport>
<title>A title</title>
</lire:subreport>
</lire:report>
EOF
$self->assert_deep_equals( [ 'lire:report', 'lire:subreport', 'A title' ],
$result );
}
sub test_parsefile {
my $self = $_[0];
my $dir = tempdir( 'parsefile_XXXXXX', 'CLEANUP' => 1 );
create_file( "$dir/test.xml", <<EOF );
<?xml version="1.0"?>
<lire:report xmlns:lire="http://www.logreport.org/">
<lire:subreport>
<title>A title</title>
</lire:subreport>
</lire:report>
EOF
my $parser = tests::XMLParserTest::BasicParser->new();
$self->assert_deep_equals( [ 'lire:report', 'lire:subreport', 'A title' ],
$parser->parsefile( "$dir/test.xml" ) );
}
package tests::XMLParserTest::BasicParser;
use base qw/Lire::XMLParser/;
sub namespaces {
return { 'http://www.logreport.org/' => 'lire' };
}
sub elements_spec {
return { 'lire:report' => [ 'lire:subreport' ],
'lire:subreport' => { 'content' => [ 'title' ],
'start' => 'report_start' },
'title' => [ 'PCDATA' ] };
}
sub parse_start {
my $self = $_[0];
$self->{'result'} = [];
return;
}
sub parse_end {
return $_[0]{'result'};
}
sub report_start {
my ( $self, $name, $attr ) = @_;
push @{$self->{'result'}}, $name;
}
sub title_char {
my ( $self, $text ) = @_;
push @{$self->{'result'}}, $text;
}
1;
syntax highlighted by Code2HTML, v. 0.9.1