#!/usr/bin/perl -w
use strict;
use strict;
use Carp;
use File::Spec;
use lib File::Spec->catdir(File::Spec->curdir,"t");
use tools;
$|=1;
my $DEBUG=0;
use lib File::Spec->catdir(File::Spec->curdir,"blib/lib");
use XML::Twig;
my $TMAX=181;
print "1..$TMAX\n";
{ # testing how well embedded comments and pi's are kept when changing the content
my @tests= ( [ "foo bar baz", "foo bar", "foo bar" ],
[ "foo bar baz", "foo bar baz foobar", "foo bar baz foobar" ],
[ "foo bar foobar tutu", "bar tutu", "bar tutu" ],
[ "foo bar foobar baz", "foobar baz", "foobar baz"],
[ "foo baz", "foo bar baz", "foo bar baz"],
[ "foo baz", "foo bar baz", "foo bar baz"],
[ "foo bar baz", "bar baz", "bar baz"],
[ "foo bar baz toto", "foo toto", "foo toto"],
);
foreach my $test (@tests)
{ my( $initial, $set, $expected)= @$test;
my $t= XML::Twig->nparse( "$initial");
$t->root->set_content( $set);
is( $t->sprint, "$expected", "set_content '$initial' => '$set'");
}
}
{ # RT #17145
my $twig= new XML::Twig()->parse("");
is( scalar( $twig->get_xpath('//root/elt[1]/child')), 0, "Context position of non-existent elements in XPATH expressions");
}
{ # some extra coverage
my @siblings= XML::Twig->nparse( "")->root->following_elts;
is( scalar( @siblings), 0, "following_elts on last sibling");
is( XML::Twig->nparse( "")->root->del_id->sprint, "", "del_id on elt with no atts");
# next_elt with deep tree (
my $t= XML::Twig->nparse( q{
});
foreach my $e ($t->root->descendants_or_self)
{ is( scalar( $e->_descendants), $e->att( 'n'), "_descendant " . $e->tag . "\n");
is( scalar( $e->_descendants( 1)), $e->att( 'n') + 1, "_descendant(1) " . $e->tag . "\n");
}
}
{
my $exp= '/foo/1^%';
eval { XML::Twig->nparse( "")->get_xpath( $exp); };
matches( $@, "^error in xpath expression", "xpath with valid expression then stuff left");
}
{
my $t = XML::Twig->nparse( "");
my $root = $t->root;
my $elt =XML::Twig::Elt->new( 'foo');
foreach my $pos ( qw( before after))
{ eval { $elt->paste( $pos => $root); };
matches( $@, "^cannot paste $pos root", "paste $pos root");
eval " \$elt->paste_$pos( \$root)";
matches( $@, "^cannot paste $pos root", "paste $pos root");
}
}
{ is( XML::Twig->nparse( comments => "process", pi => "process", "")->_dump,
"document\n|-doc\n| |-COMMENT: ''\n| |-PI: 't' - 'data'\n| |-PI: 't' - ''\n",
"_dump PI/comment"
);
}
{ is( XML::Twig->nparse( '')->root->get_xpath( '.', 0)->gi, 'doc', 'get_xpath: .'); }
{ my $t= XML::Twig->nparse( '');
$t->first_elt( '#CDATA')->set_text( 'bar');
is( $t->sprint, '', " set_text on CDATA");
$t->root->set_text( 'bar');
is( $t->sprint, 'bar', " set_text on elt containing CDATA");
$t= XML::Twig->nparse( '');
$t->first_elt( '#CDATA')->set_text( 'bar', force_pcdata => 1);
is( $t->sprint, 'bar', " set_text on CDATA with force_pcdata");}
# print/flush entity
# SAX export entity
{ my $enc= "a_non_existent_encoding_bwaaahhh";
eval { XML::Twig->iconv_convert( $enc); };
matches( $@, "^(Unsupported|Text::Iconv not available|Can't locate)", "unsupported encoding");
}
{ # test comments handlers
my $doc= qq{};
is( XML::Twig->nparse( twig_handlers => { '#COMMENT' => sub { return uc( $_[1]); } }, $doc)->sprint,
qq{},
"comment handler"
);
is( XML::Twig->nparse( twig_handlers => { '#COMMENT' => sub { return uc( $_[1]); } }, keep_encoding => 1, $doc)->sprint,
qq{},
"comment handler (with keep_encoding)"
);
is( XML::Twig->nparse( twig_handlers => { '#COMMENT' => sub { return; } }, keep_encoding => 0, $doc)->sprint,
qq{},
"comment handler returning undef comment"
);
is( XML::Twig->nparse( twig_handlers => { '#COMMENT' => sub { return ''; } }, keep_encoding => 1, $doc)->sprint,
qq{},
"comment handler returning empty comment (with keep_encoding)"
);
is( XML::Twig->nparse( comments => 'process', twig_handlers => { '#COMMENT' => sub { $_->set_comment( uc( $_->comment)); } },
keep_encoding => 0, $doc)->sprint,
qq{},
"comment handler, process mode"
);
is( XML::Twig->nparse( comments => 'process', twig_handlers => { '#COMMENT' => sub { $_->set_comment( uc( $_->comment)); } },
keep_encoding => 1, $doc)->sprint,
qq{},
"comment handler (with keep_encoding), process mode"
);
is( XML::Twig->nparse( comments => 'process', twig_handlers => { elt => sub { $_->cut; } }, keep_encoding => 0, $doc)->sprint,
qq{},
"comment handler deletes comment, process mode"
);
is( XML::Twig->nparse( comments => 'process', twig_handlers => { '#COMMENT' => sub { $_->cut; } }, keep_encoding => 0, $doc)->sprint,
qq{},
"comment handler deletes comment, process mode"
);
is( XML::Twig->nparse( comments => 'process', twig_handlers => { '#COMMENT' => sub { $_->set_comment( ''); } }, keep_encoding => 1, $doc)->sprint,
qq{},
"comment handler returning empty comment (with keep_encoding), process mode"
);
}
{ # check pi element handler in keep_encoding mode
is( XML::Twig->nparse( pi => 'process', twig_handlers => { '?t' => sub { $_->set_data( uc( $_->data)); } }, '')->sprint,
'', 'pi element handler');
is( XML::Twig->nparse( pi => 'process', keep_encoding => 1,twig_handlers => { '?t' => sub { $_->set_data( uc( $_->data)); } },
'')->sprint,
'', 'pi element handler in keep_encoding mode');
}
{ # test changes on comments before the root element
my $doc= q{};
is( XML::Twig->nparse( $doc)->sprint, $doc, 'comment after root element');
is_like( XML::Twig->nparse( pi => 'process', comments => 'process', $doc)->sprint, $doc, 'comment before root element (pi/comment => process)');
is_like( XML::Twig->nparse( pi => 'process', $doc)->sprint, $doc, 'comment before root element (pi => process)');
is_like( XML::Twig->nparse( comments => 'process', $doc)->sprint, $doc, 'comment before root element (comment => process)');
}
{ # test bug on comments after the root element RT #17064
my $doc= q{};
is( XML::Twig->nparse( $doc)->sprint, $doc, 'comment after root element');
is( XML::Twig->nparse( pi => 'process', comments => 'process', $doc)->sprint, $doc, 'comment after root element (pi/comment => process)');
is_like( XML::Twig->nparse( pi => 'process', $doc)->sprint, $doc, 'comment before root element (pi => process)');
is_like( XML::Twig->nparse( comments => 'process', $doc)->sprint, $doc, 'comment before root element (comment => process)');
}
{ # test bug on doctype declaration (RT #17044)
my $doc= qq{\n};
is( XML::Twig->nparse( $doc)->sprint, $doc, "doctype with public id");
is( XML::Twig->nparse( $doc)->sprint( Update_DTD => 1), $doc, "doctype with public id (update_DTD => 1)");
$doc= qq{\n};
is( XML::Twig->nparse( $doc)->sprint, $doc, "doctype with public id");
is( XML::Twig->nparse( $doc)->sprint( updateDTD => 1) , $doc, "doctype with public id (update_DTD => 1)");
}
{ # test bug on tag names similar to internal names RT #16540
ok( XML::Twig->nparse( twig_handlers => { level => sub {} }, ''), " bug on tag names similar to internal names RT #16540");
}
{ # test parsing of an html string
if( XML::Twig::_use( 'HTML::TreeBuilder', 3.13) && XML::Twig::_use( 'HTML::Entities::Numbered'))
{
ok( XML::Twig->parse( error_context => 1, '
foo
bar
été
'), "parsing an html string");
}
else
{ skip( 1, "need HTML::TreeBuilder 3.13+ and HTML::Entities::Numbered for this test"); }
}
{ # testing print_to_file
my $tmp= "print_to_file.xml";
my $doc= "foo";
unlink( $tmp); # no check, it could not be there
my $t1= XML::Twig->nparse( $doc)->print_to_file( $tmp);
ok( -f $tmp, "print_to_file created document");
my $t2= XML::Twig->nparse( $tmp);
is( $t2->sprint, $t1->sprint, "generated document identical to original document");
unlink( $tmp);
my $e1= XML::Twig->parse( 'foobar')->first_elt( 'b')->print_to_file( $tmp);
ok( -f $tmp, "print_to_file on elt created document");
$t2= XML::Twig->nparse( $tmp);
is( $t2->sprint, 'bar', "generated sub-document identical to original sub-document");
unlink( $tmp);
# failure modes
eval { XML::Twig->nparse( $tmp); };
mtest( $@, "Couldn't open $tmp:");
my $non_existent="non_existent_I_hope_01/tmp";
while( -f $non_existent) { $non_existent++; } # most likely unnecessary ;--)
eval { $t1->print_to_file( $non_existent); };
mtest( $@, "cannot create file $non_existent:");
}
{
my $doc=q{};
my $t= XML::Twig->nparse( $doc);
test_get_xpath( $t, q{/doc/elt[1][@att2="v2"]}, '');
}
{ my $doc=q{foobarbazfoobar};
my $t= XML::Twig->nparse( $doc);
test_get_xpath( $t, q{/d/e[@a="1"][2]}, 'e2');
test_get_xpath( $t, q{/d/e[@a="1"][-2]}, 'e2');
test_get_xpath( $t, q{/d/e[@a="1"][-1]}, 'e4');
test_get_xpath( $t, q{/d/e[@a="1"][-3]}, 'e1');
}
{ # test support for new conditions condition in get_xpath
my $doc=q{foobarbaz};
my $t= XML::Twig->nparse( $doc);
# just checking
test_get_xpath( $t, q{//elt[@a]}, 'elt1');
is( ids( $t->get_xpath( q{//*[@a]})), 'd1:elt1', '//*[@a] xpath exp');
# test support for !@att condition in get_xpath
is( ids( $t->get_xpath( q{//elt[!@a]})), 'elt2:elt3', '//elt[!@a] xpath exp');
is( ids( $t->get_xpath( q{//elt[not@a]})), 'elt2:elt3', '//elt[not@a] xpath exp');
is( ids( $t->get_xpath( q{/doc/elt[not@a]})), 'elt2:elt3', '/doc/elt[not@a] xpath exp');
is( ids( $t->get_xpath( q{//*[!@a]})), 'elt2:elt3', '//*[!@a] xpath exp');
is( ids( $t->get_xpath( q{//*[not @a]})), 'elt2:elt3', '//*[not @a] xpath exp');
is( ids( $t->get_xpath( q{/doc/*[not @a]})), 'elt2:elt3', '/doc/*[not @a] xpath exp');
# support for ( and )
test_get_xpath( $t, q{//*[@id="d1" or @a and @id="elt1"]}, 'd1:elt1');
test_get_xpath( $t, q{//*[(@id="d1" or @a) and @id="elt1"]}, 'elt1');
}
{ # more test on new XPath support: axis in node test part
my $doc=q{};
my $t= XML::Twig->nparse( $doc);
# parent axis in node test part
test_get_xpath( $t, q{/doc//selt/..}, 'elt1:elta1');
test_get_xpath( $t, q{/doc//selt/parent::elt}, 'elt1');
test_get_xpath( $t, q{/doc//selt/parent::elta}, 'elta1');
test_get_xpath( $t, q{//sseltb/ancestor::eltc}, 'eltc1');
test_get_xpath( $t, q{//sseltb/ancestor::*}, 'd1:eltb1:seltb1:eltc1:seltb2');
test_get_xpath( $t, q{//sseltb/ancestor-or-self::eltc}, 'eltc1');
test_get_xpath( $t, q{//sseltb/ancestor-or-self::*}, 'd1:eltb1:seltb1:sseltb1:eltc1:seltb2:sseltb2');
test_get_xpath( $t, q{/doc//*[@id="eltc1"]/descendant::*}, 'seltb2:sseltb2');
test_get_xpath( $t, q{/doc//*[@id="eltc1"]/descendant::sseltb}, 'sseltb2');
test_get_xpath( $t, q{/doc//*[@id="eltc1"]/descendant::eltc}, '');
test_get_xpath( $t, q{/doc//*[@id="eltc1"]/descendant-or-self::*}, 'eltc1:seltb2:sseltb2');
test_get_xpath( $t, q{/doc//*[@id="eltc1"]/descendant-or-self::eltc}, 'eltc1');
test_get_xpath( $t, q{/doc//*[@id="eltc1"]/descendant-or-self::seltb}, 'seltb2');
test_get_xpath( $t, q{/doc/elt/following-sibling::*}, 'elta1:elt2:eltb1:eltc1');
test_get_xpath( $t, q{/doc/elt/preceding-sibling::*}, 'elt1:elta1');
test_get_xpath( $t, q{/doc/elt[@id="elt1"]/preceding-sibling::*}, '');
test_get_xpath( $t, q{/doc/elt/following-sibling::elt}, 'elt2');
test_get_xpath( $t, q{/doc/elt/preceding-sibling::elt}, 'elt1');
test_get_xpath( $t, q{/doc/elt[@id="elt1"]/preceding-sibling::elt}, '');
is( $t->elt_id( "sseltb1")->following_elt->id, 'eltc1', 'following_elt');
is( ids( $t->elt_id( "sseltb1")->following_elts), 'eltc1:seltb2:sseltb2', 'following_elts');
is( ids( $t->elt_id( "sseltb1")->following_elts( '')), 'eltc1:seltb2:sseltb2', 'following_elts( "")');
my @elts= $t->elt_id( "eltc1")->descendants_or_self;
is( ids( @elts), 'eltc1:seltb2:sseltb2', 'descendants_or_self');
is( ids( XML::Twig::_unique_elts( @elts)), 'eltc1:seltb2:sseltb2', '_unique_elts');
test_get_xpath( $t, q{/doc//[@id="sseltb1"]/following::*}, 'eltc1:seltb2:sseltb2');
test_get_xpath( $t, q{/doc//[@id="sseltb1"]/following::seltb}, 'seltb2');
test_get_xpath( $t, q{/doc//[@id="selt1"]/following::elt}, 'elt2');
ok( $t->root->last_descendant( 'doc'), "checking if last_descendant returns the element itself");
test_get_xpath( $t, q{/doc/preceding::*}, '');
test_get_xpath( $t, q{/doc/elt[1]/preceding::*}, '');
test_get_xpath( $t, q{/doc/elt/preceding::*}, 'd1:elt1:selt1:elta1:selt2');
test_get_xpath( $t, q{/doc//[@id="sseltb2"]/preceding::seltb}, 'seltb1');
test_get_xpath( $t, q{/doc//[@id="selt1"]/preceding::elt}, '');
test_get_xpath( $t, q{/doc//[@id="selt2"]/preceding::elt}, 'elt1');
test_get_xpath( $t, q{/doc/self::doc}, 'd1');
test_get_xpath( $t, q{/doc/self::*}, 'd1');
test_get_xpath( $t, q{/doc/self::elt}, '');
test_get_xpath( $t, q{//[@id="selt1"]/self::*}, 'selt1');
test_get_xpath( $t, q{//[@id="selt1"]/self::selt}, 'selt1');
test_get_xpath( $t, q{//[@id="selt1"]/self::elt}, '');
}
{ # more tests: more than 1 predicate
my $doc=q{};
my $t= XML::Twig->nparse( $doc);
test_get_xpath( $t, q{/doc/elt[@id][@att="v1"]}, 'elt1:elt2');
test_get_xpath( $t, q{/doc/elt[@id][@att2="v1"]}, 'elt1');
test_get_xpath( $t, q{/doc/elt[@id][1]}, 'elt1');
test_get_xpath( $t, q{/doc/elt[@att="v1"][1]}, 'elt1');
test_get_xpath( $t, q{/doc/elt[@att="v2"][1]}, '');
test_get_xpath( $t, q{/doc/elt[@att="v1"][2]}, 'elt2');
test_get_xpath( $t, q{/doc/elt[1][@att2="v1"]}, 'elt1');
test_get_xpath( $t, q{/doc/elt[1][@att2="v2"]}, '');
test_get_xpath( $t, q{/doc/elt[@att2="v2"][1]}, 'elt2');
test_get_xpath( $t, q{/doc/elt[@att2="v2"][2]}, '');
test_get_xpath( $t, q{/doc/elt[@att2][1]}, 'elt1');
test_get_xpath( $t, q{/doc/elt[@att2][2]}, 'elt2');
test_get_xpath( $t, q{/doc/elt[@att2][3]}, '');
test_get_xpath( $t, q{/doc/elt[@att2][-1]}, 'elt2');
test_get_xpath( $t, q{/doc/elt[@att2][-2]}, 'elt1');
test_get_xpath( $t, q{/doc/elt[@att2][-3]}, '');
}
{ # testing creation of elements in the proper class
package foo; use base 'XML::Twig::Elt'; package main;
my $t= XML::Twig->new( elt_class => "foo")->parse( '');
my $elt= $t->first_elt( 'elt');
$elt->set_text( 'bar');
is( $elt->first_child->text, 'bar', "content of element created with set_text");
is( ref( $elt->first_child), 'foo', "class of element created with set_text");
$elt->set_content( 'baz');
is( $elt->first_child->text, 'baz', "content of element created with set_content");
is( ref( $elt->first_child), 'foo', "class of element created with set_content");
$elt->insert( 'toto');
is( $elt->first_child->tag, 'toto', "tag of element created with set_content");
is( ref( $elt->first_child), 'foo', "class of element created with insert");
$elt->insert_new_elt( first_child => 'tata');
is( $elt->first_child->tag, 'tata', "tag of element created with insert_new_elt");
is( ref( $elt->first_child), 'foo', "class of element created with insert");
$elt->wrap_in( 'tutu');
is( $t->root->first_child->tag, 'tutu', "tag of element created with wrap_in");
is( ref( $t->root->first_child), 'foo', "class of element created with wrap_in");
$elt->prefix( 'titi');
is( $elt->first_child->text, 'titi', "content of element created with prefix");
is( ref( $elt->first_child), 'foo', "class of element created with prefix");
$elt->suffix( 'foobar');
is( $elt->last_child->text, 'foobar', "content of element created with suffix");
is( ref( $elt->last_child), 'foo', "class of element created with suffix");
$elt->last_child->split_at( 3);
is( $elt->last_child->text, 'bar', "content of element created with split_at");
is( ref( $elt->last_child), 'foo', "class of element created with split_at");
is( ref( $elt->copy), 'foo', "class of element created with copy");
$t= XML::Twig->new( elt_class => "foo")->parse( 'toto');
$t->root->subs_text( qr{(to)} => '&elt( p => $1)');
is( $t->sprint, '
to
to
', "subs_text result");
my $result= join( '-', map { join( ":", ref($_), $_->tag) } $t->root->descendants);
is( $result, "foo:p-foo:#PCDATA-foo:p-foo:#PCDATA", "subs_text classes and tags");
}
{ # wrap children with > in attribute
my $doc=q{};
my $result =XML::Twig->new->parse( $doc)->root->wrap_children( '+', "w")->strip_att( 'id')->sprint;
my $expected = q{};
is( $result => $expected, "wrap_children with > in attributes");
$result =XML::Twig->new->parse( $doc)->root->wrap_children( '+', "w")->strip_att( 'id')->sprint;
$expected = q{};
is( $result => $expected, "wrap_children with > in attributes, > in condition");
$result =XML::Twig->new->parse( $doc)->root->wrap_children( '+', "w")->strip_att( 'id')->sprint;
$expected = q{};
is( $result => $expected, "wrap_children with > in attributes un-escaped > in condition");
$result =XML::Twig->new->parse( $doc)->root->wrap_children( '+', "w")->strip_att( 'id')->sprint;
$expected = q{};
is( $result => $expected, "wrap_children with > in attributes, 2 atts in condition");
$result =XML::Twig->new->parse( $doc)->root->wrap_children( '+', "w")->strip_att( 'id')->sprint;
$expected = q{};
is( $result => $expected, "wrap_children with > in attributes, 2 atts in condition (no child matches)");
}
{ # test improvements to wrap_children
my $doc= q{okNOK};
my $expected= q{okNOK};
my $t= XML::Twig->new->parse( $doc);
$t->root->wrap_children( '+', w => { a => "&" });
$t->root->strip_att( 'id');
is( $t->sprint, $expected, "wrap_children with &");
}
{ # test bug on tests on attributes with a value of 0 (RT #15671)
my $t= XML::Twig->nparse( '');
my $root = $t->root();
is( scalar $root->children('*[@id="1"]'), 1, 'testing @att="1"');
is( scalar $root->children('*[@id="0"]'), 1, 'testing @att="0"');
is( scalar $root->children('*[@id="0" or @id="1"]'), 2, 'testing @att="0" or');
is( scalar $root->children('*[@id="0" and @id="1"]'), 0, 'testing @att="0" and');
}
{ # test that the '>' after the doctype is properly output when there is no DTD RT#
my $doctype='';
my $doc="$doctype";
is_like( XML::Twig->nparse( $doc)->sprint, $doc);
is_like( XML::Twig->nparse( $doc)->doctype, $doctype);
}