#!/usr/local/lib/perl -w use strict; #use Devel::TraceSAX; use Carp; use Test; use XML::Filter::Dispatcher qw( :all ); use UNIVERSAL; my $have_test_diff = eval "use Test::Differences; 1"; my $a = QB->new( "a", "" ); my @nodes_in_a = ( "", "a" ); my $abcd = QB->new( "abcd", "stuvwxyz" ); my $nodes_in_abcd = 25; ## Including the doc node :) my @nodes_in_abcd = ( "", qw( R RRrr a s S SSss b t T TTtt c u d name id v d id w x y z Z1 Z1Z1z1z1 ) ); my @non_attr_nodes_in_abcd = ( "", qw( R RRrr a s S SSss b t T TTtt c u d v d w x y z Z1 Z1Z1z1z1 ) ); my @non_doc_non_attr_nodes_in_abcd = ( qw( R RRrr a s S SSss b t T TTtt c u d v d w x y z Z1 Z1Z1z1z1 ) ); my @end_nodes_in_abcd = ( qw( R RRrr s S SSss t T TTtt u v d w d x c y b z a Z1 Z1Z1z1z1 ), "" ); my @elt_end_nodes_in_abcd = ( qw( d d c b a ) ); my $abcdBcd = QB->new( "abcdBcd", "1234" ); my $abc123 = QB->new( "abc123", "123321" ); my $ab = QB->new( "ab", "bA" ); my $var = QB->new( "var", "" ); my @nodes_in_var = ( "", qw( a b ) ); my $aaaabaa = QB->new( "aaaabaa", "" ); my $aaaaaab = QB->new( "aaaaaab", "" ); my $aaacb = QB->new( "aaacb", "" ); my $aaaacb = QB->new( "aaaacb", "" ); my $ns = QB->new( "ns", "" ); sub result_list { my $prefix = ""; $prefix = shift() . "_" unless ref $_[0]; my $suffix = ""; $suffix = "_" . pop unless ref $_[-1]; return [ map "$prefix$_$suffix", @{$_[0]} ] } my @log; my $fold_constants; sub rules { my @out; while ( @_ ) { push @out, shift; if ( ! @_ || ! ref $_[0] ) { push @out, sub { my ( $self ) = shift; my ( $foo ) = @_; my $xr = xvalue; push @log, join( "", ( $foo->{Name} || ( $foo->{Target} || "" ) . ( $foo->{Data} || "" ) ), defined $xr && ( ref $xr eq "" || ref $xr eq "SCALAR" ) ? ( "_", ref $xr ? $$xr : $xr ) : (), ); }; next; } push @out, [ rules( @{shift()} ) ]; } return @out; } sub d { my $qb = shift; my $rule = shift; my $options = @_ && ref( $_[-1] ) eq "HASH" ? pop : {}; $options->{FoldConstants} = $fold_constants; my $expect = result_list @_; unless ( $have_test_diff ) { @_ = ( "Need Test::Differences to test", 1 ); goto &skip; } my @rules = rules ref $rule ? @$rule : $rule; #use Data::Dumper ; warn Dumper( \@rules ); my $d = eval { XML::Filter::Dispatcher->new( Rules => \@rules, Vars => { foo => [ boolean => "bar" ], }, %$options, ) }; @log = (); if ( $d ) { $qb->playback( $d ); } else { push @log, split /\n/, $@; } @_ = ( \@log, $expect, $rule ); goto &eq_or_diff; } ## NOTE: if you try this at home, it is *not* unsupported. @XFD::Function::oops::ISA = qw( XFD::BooleanFunction ); sub XFD::Function::oops::as_immed_code { "Carp::confess( 'operator not shorted!' )"; } ## Laid out for wide terminals, sorry. This code is too tabular to do otherwise my @tests = ( ## Numbers and string literals sub { d $a, '0', [ '' ], '0' }, ## Note: we do not do '-0' in Perl... sub { d $a, '-0', [ '' ], '0' }, sub { d $a, '10', [ '' ], '10' }, sub { d $a, '-10', [ '' ], '-10' }, sub { d $a, '""', [ '' ], '' }, sub { d $a, '"string"', [ '' ], 'string' }, ## Functions sub { d $a, 'concat(boolean(0),"P")', [ '' ], 'falseP' }, sub { d $a, 'concat(boolean(false()),"P")', [ '' ], 'falseP' }, sub { d $a, 'concat(boolean(""),"P")', [ '' ], 'falseP' }, sub { d $a, 'boolean(1)', [ '' ], 'true' }, sub { d $a, 'boolean(true())', [ '' ], 'true' }, sub { d $a, 'boolean("0")', [ '' ], 'true' }, sub { d $a, 'boolean("false")', [ '' ], 'true' }, sub { d $a, 'ceiling(1)', [ '' ], '1' }, sub { d $a, 'ceiling(0.49)', [ '' ], '1' }, sub { d $a, 'ceiling(0.999)', [ '' ], '1' }, sub { d $a, 'ceiling(-2.999)', [ '' ], '-2' }, sub { d $a, 'concat("a","b","c","d")', [ '' ], 'abcd' }, sub { d $a, 'concat(1,2.3)', [ '' ], '12.3' }, sub { d $a, 'concat(true(),false())', [ '' ], 'truefalse' }, sub { d $a, 'contains("ab","a")', [ '' ], 'true' }, sub { d $a, 'contains("ab","b")', [ '' ], 'true' }, # tested below as a predicate #sub { d $abcd, 'is-end-event()', \@end_nodes_in_abcd, 'true' }, sub { d $a, 'string(false())', [ '' ], 'false' }, sub { d $a, 'concat(floor(0),"P")', [ '' ], '0P' }, sub { d $a, 'concat(floor(0.5),"P")', [ '' ], '0P' }, sub { d $a, 'concat(floor(0.999),"P")', [ '' ], '0P' }, sub { d $a, 'concat(floor(-0.999),"P")', [ '' ], '-1P' }, sub { d $a, "normalize-space(' \t\r\na \t\r\nb \t\r\n')", [ '' ], 'a b' }, sub { d $a, 'not(0)', [ '' ], 'true' }, sub { d $a, 'concat(not(1),"P")', [ '' ], 'falseP' }, sub { d $a, 'not(0)', [ '' ], 'true' }, sub { d $a, 'number(1)', [ '' ], '1' }, sub { d $a, 'number(true())', [ '' ], '1' }, sub { d $a, 'number(" 1 ")', [ '' ], '1' }, sub { d $abc123, 'number(.)', [ '' ], '123321' }, sub { d $abc123, 'number()', [ '' ], '123321' }, sub { d $ns, 'local-name()', [ '_' ], }, sub { d $ns, 'local-name(a)', [ '_a' ], }, sub { d $ns, 'local-name(//bar:b)', [ '_b' ], { Namespaces => { bar => "foo-ns", }, } }, sub { d $ns, 'name()', [ '_' ], }, sub { d $ns, 'name(a)', [ '_a' ], }, sub { d $ns, 'name(//bar:b)', [ '_foo:b' ], { Namespaces => { bar => "foo-ns", }, } }, sub { d $ns, 'namespace-uri()', [ '_' ], }, sub { d $ns, 'namespace-uri(a)', [ '_default-ns' ], }, sub { d $ns, 'namespace-uri(//bar:b)', [ '_foo-ns' ], { Namespaces => { bar => "foo-ns", }, } }, sub { d $a, 'concat(round(0),"P")', [ '' ], '0P' }, sub { d $a, 'concat(round(0.5),"P")', [ '' ], '1P' }, sub { d $a, 'concat(round(0.999),"P")', [ '' ], '1P' }, sub { d $a, 'concat(round(-0.999),"P")', [ '' ], '-1P' }, sub { d $a, "normalize-space(' \t\r\na \t\r\nb \t\r\n')", [ '' ], 'a b' }, sub { d $ab, 'normalize-space(.)', [ '' ], 'bA' }, sub { d $ab, 'normalize-space()', [ '' ], 'bA' }, sub { d $a, 'true()', [ '' ], 'true' }, sub { d $a, 'starts-with("ab","a")', [ '' ], 'true' }, sub { d $a, 'starts-with("ab","b")', [ '' ], 'false' }, sub { d $a, 'string("a")', [ '' ], 'a' }, sub { d $a, 'string(true())', [ '' ], 'true' }, sub { d $a, 'string(01)', [ '' ], '1' }, sub { d $a, 'string-length("ab")', [ '' ], '2' }, sub { d $ab, 'string-length(.)', [ '' ], '2' }, sub { d $ab, 'string-length()', [ '' ], '2' }, sub { d $a, 'substring("ab",0)', [ '' ], 'ab' }, sub { d $a, 'substring("ab",1)', [ '' ], 'ab' }, sub { d $a, 'substring("ab",2)', [ '' ], 'b' }, sub { d $a, 'concat(substring("ab",3),1)', [ '' ], '1' }, sub { d $a, 'substring("12345",2,3)', [ '' ], '234' }, sub { d $a, 'substring("12345",2)', [ '' ], '2345' }, sub { d $a, 'substring("12345",1.5,2.6)', [ '' ], '234' }, sub { d $a, 'substring("12345",0,3)', [ '' ], '12' }, # Perl doesn't handle Inf and NaN right, so... #sub { d $a, 'substring("12345",0 div 0,3)', [ '' ], 'P' }, #sub { d $a, 'substring("12345",1,0 div 0)', [ '' ], 'P' }, #sub { d $a, 'substring("12345",-42,1 div 0)', [ '' ], '12345' }, #sub { d $a, 'concat(substring("12345",-1 div 0,1 div 0),"P")', [ '' ], 'P' }, sub { d $a, 'substring-after("ab","a")', [ '' ], 'b' }, sub { d $a, 'concat(substring-after("ab","b"),1)', [ '' ], '1' }, sub { d $a, 'concat(substring-after("ab","c"),1)', [ '' ], '1' }, sub { d $a, 'concat(substring-after("ab",""),1)', [ '' ], 'ab1' }, sub { d $a, 'substring-after("1999/04/01","19")', [ '' ], '99/04/01' }, sub { d $a, 'substring-before("ab","b")', [ '' ], 'a' }, sub { d $a, 'substring-before("1999/04/01","/")', [ '' ], '1999' }, sub { d $a, 'concat(substring-before("ab","a"),1)', [ '' ], '1' }, sub { d $a, 'concat(substring-before("ab","c"),1)', [ '' ], '1' }, sub { d $a, 'concat(substring-before("ab",""),1)', [ '' ], '1' }, sub { d $a, 'translate("bar","abc","ABC")', [ '' ], 'BAr' }, sub { d $a, 'translate("--aaa--","abc-","ABC")', [ '' ], 'AAA' }, ## Operators (other than union) sub { d $a, 'concat( 0 or 0, "P" )', [ '' ], 'falseP' }, sub { d $a, '0 or 1', [ '' ], 'true' }, sub { d $a, '1 or 0', [ '' ], 'true' }, sub { d $a, '1 or 1', [ '' ], 'true' }, sub { d $a, '1 or oops()', [ '' ], 'true' }, sub { d $a, 'concat( 0 and 0, "P" )', [ '' ], 'falseP' }, sub { d $a, 'concat( 0 and 1, "P" )', [ '' ], 'falseP' }, sub { d $a, 'concat( 1 and 0, "P" )', [ '' ], 'falseP' }, sub { d $a, '1 and 1', [ '' ], 'true' }, sub { d $a, 'concat( 0 and oops(), "P" )', [ '' ], 'falseP' }, sub { d $a, '0 and 1 or 1', [ '' ], 'true' }, sub { d $a, '1 or 1 and 0', [ '' ], 'true' }, sub { d $a, 'concat( true() = false(), "P" )', [ '' ], 'falseP' }, sub { d $a, 'true() = true()', [ '' ], 'true' }, sub { d $a, '1 = 1', [ '' ], 'true' }, sub { d $a, '"a" = "a"', [ '' ], 'true' }, sub { d $a, '1 = " 1 "', [ '' ], 'true' }, sub { d $a, 'true() = 1', [ '' ], 'true' }, sub { d $a, 'false() = 0', [ '' ], 'true' }, sub { d $a, 'true() = "a"', [ '' ], 'true' }, sub { d $a, 'false() = ""', [ '' ], 'true' }, sub { d $a, 'concat( true() != true(), "P" )', [ '' ], 'falseP' }, sub { d $a, 'true() != false()', [ '' ], 'true' }, sub { d $a, '1 != 0', [ '' ], 'true' }, sub { d $a, '"a" != "b"', [ '' ], 'true' }, sub { d $a, '1 != " 0 "', [ '' ], 'true' }, sub { d $a, 'true() != 0', [ '' ], 'true' }, sub { d $a, 'false() != 1', [ '' ], 'true' }, sub { d $a, 'true() != ""', [ '' ], 'true' }, sub { d $a, 'false() != "a"', [ '' ], 'true' }, sub { d $a, 'concat( true() < true(), "P" )', [ '' ], 'falseP' }, sub { d $a, 'concat( true() < false(), "P" )', [ '' ], 'falseP' }, sub { d $a, 'false() < true()', [ '' ], 'true' }, sub { d $a, '0 < 1', [ '' ], 'true' }, sub { d $a, '"a" < "b"', [ '' ], 'true' }, sub { d $a, '0 < " 1 "', [ '' ], 'true' }, sub { d $a, 'true() <= true()', [ '' ], 'true' }, sub { d $a, 'concat( true() <= false(), "P" )', [ '' ], 'falseP' }, sub { d $a, 'false() <= true()', [ '' ], 'true' }, sub { d $a, '0 <= 1', [ '' ], 'true' }, sub { d $a, '"a" <= "b"', [ '' ], 'true' }, sub { d $a, '0 <= " 1 "', [ '' ], 'true' }, sub { d $a, 'concat( true() > true(), "P" )', [ '' ], 'falseP' }, sub { d $a, 'concat( false() > true(), "P" )', [ '' ], 'falseP' }, sub { d $a, 'true() > false()', [ '' ], 'true' }, sub { d $a, '1 > 0', [ '' ], 'true' }, sub { d $a, '"b" > "a"', [ '' ], 'true' }, sub { d $a, '1 > " 0 "', [ '' ], 'true' }, sub { d $a, 'concat( 3 > 2 > 1, "P" )', [ '' ], 'falseP' }, sub { d $a, 'true() >= true()', [ '' ], 'true' }, sub { d $a, 'concat( false() >= true(), "P" )', [ '' ], 'falseP' }, sub { d $a, 'true() >= false()', [ '' ], 'true' }, sub { d $a, '1 >= 0', [ '' ], 'true' }, sub { d $a, '"b" >= "a"', [ '' ], 'true' }, sub { d $a, '1 >= " 0 "', [ '' ], 'true' }, sub { d $a, '4 + 1', [ '' ], '5' }, sub { d $a, '4 - 1', [ '' ], '3' }, sub { d $a, '4 * 1', [ '' ], '4' }, sub { d $a, '4 div 2', [ '' ], '2' }, sub { d $a, '5 mod 2', [ '' ], '1' }, sub { d $a, '( 1 )', [ '' ], '1' }, sub { d $a, '- ( 1 )', [ '' ], '-1' }, ## ## Location paths ## sub { d $abcd, '/', [ '' ] }, sub { d $abcd, '/.', [ '' ] }, sub { d $abcd, '/child::a', [ 'a' ] }, sub { d $abcd, '/a', [ 'a' ] }, sub { d $abcd, 'a', [ 'a' ] }, sub { d $abcd, './a', [ 'a' ] }, sub { d $abcd, '.', [ ''] }, sub { d $abcd, '//b', [ 'b' ] }, sub { d $abcd, 'b', [ 'b' ] }, sub { d $abcd, '//./b', [ 'b' ] }, sub { d $abcd, 'd', [ 'd', 'd' ] }, ## This next one tests to make sure 'b' doesn't fire twice sub { d $abcd, '//.//b', [ 'b' ] }, sub { d $abcd, '/a/b/c', [ 'c' ] }, sub { d $abcd, '/a/b/c/d', [ 'd', 'd' ] }, sub { d $abcd, '(((/a)/b)/c)/d', [ 'd', 'd' ] }, ##sub { d $abcd, '/*', [ 'a' ] }, sub { d $abcd, '/child::*', [ 'a' ] }, sub { d $abcd, '/*/child::*', [ 'b' ] }, sub { d $abcd, '*', [ 'a', 'b', 'c', 'd', 'd' ] }, ## ## //descendant-or-self::node() ## sub { d $abcd, '/descendant-or-self::node()', \@non_attr_nodes_in_abcd }, sub { d $abcd, '/descendant-or-self::node()/node()', [ @non_attr_nodes_in_abcd[ 1..$#non_attr_nodes_in_abcd ] ] }, sub { d $abcd, '//node()', [ @non_attr_nodes_in_abcd[ 1..$#non_attr_nodes_in_abcd ] ] }, sub { d $abcd, '/descendant-or-self::node()/a', [ 'a' ] }, sub { d $abcd, '//a', [ 'a' ] }, sub { d $abcd, '/descendant-or-self::node()/b', [ 'b' ] }, sub { d $abcd, '//b', [ 'b' ] }, sub { d $abcd, '/descendant-or-self::node()/d', [ 'd', 'd' ] }, sub { d $abcd, '//d', [ 'd', 'd' ] }, sub { d $abcdBcd, '/a/B//d', [ 'd', 'd' ] }, ## TODO: fix grammar to like //// #sub { d $abcd, '////node()', [ @non_attr_nodes_in_abcd[ 1..$#non_attr_nodes_in_abcd ] ] }, sub { d $abcd, '/descendant-or-self::node()/descendant-or-self::node()/node()', [ @non_attr_nodes_in_abcd[ 1..$#non_attr_nodes_in_abcd ] ] }, sub { d $abcd, '/self::node()', [ '' ] }, sub { d $abcd, '/self::node()/a', [ 'a' ] }, sub { d $abcd, '/./a', [ 'a' ] }, sub { d $abcd, '//./a', [ 'a' ] }, sub { d $abcd, '//./d', [ 'd', 'd' ] }, sub { d $abcd, '//attribute::id', [ 'id', 'id' ] }, sub { d $abcd, '//@id', [ 'id', 'id' ] }, sub { d $abcd, '@id', [ 'id', 'id' ] }, sub { d $abcd, '//attribute::*', [ 'id', 'name', 'id' ] }, sub { d $abcd, '//@*', [ 'id', 'name', 'id' ] }, ## Node tests (other than node()) sub { d $abcd, '//text()', [qw( s t u v w x y z )] }, sub { d $abcd, '//comment()', [qw( R S T Z1 )] }, sub { d $abcd, '//processing-instruction()', [qw( RRrr SSss TTtt Z1Z1z1z1 )] }, ## Union: | sub { d $abcd, '//a|//b', [ 'a', 'b' ] }, sub { d $abcd, 'a|b', [ 'a', 'b' ] }, sub { d $abcd, '//a|//a', [ 'a' ] }, sub { d $abcd, '//a|//a|//a', [ 'a' ] }, sub { d $abcdBcd, '/a/b/c|/a/B/c', [ 'c', 'c' ] }, sub { d $abcdBcd, '(/a/b|/a/B)/c', [ 'c', 'c' ] }, ## Predicates ## TODO: sub { d $a, 'a[b]/b[c]', [ 'b' ] }, sub { d $a, 'a[1]', [ 'a' ] }, sub { d $a, 'a[0]', [] }, sub { d $abcd, '//d[@id]', [ 'd', 'd' ], }, sub { d $abcd, '//d[@id=1]', [ 'd' ], }, sub { d $abcd, 'a[b]', [ 'a' ] }, sub { d $abcd, 'a[c]', [] }, #sub { Devel::TraceCalls::trace_calls( "XML::Filter::Dispatcher->" ) }, sub { d $abcd, 'a[b]/b', [ 'b' ] }, sub { d $abcd, 'a[b]/b/c/d', [ 'd', 'd' ] }, sub { d $abcd, 'a[c]/b/c/d', [] }, ## Functions that take node sets (and thus require precursors) sub { d $ab, 'string(a)', [ '_bA' ] }, sub { d $abcd, 'string(.)', [ '_stuvwxyz' ] }, sub { d $abcd, 'string()', [ '_stuvwxyz' ] }, sub { d $abcd, 'string(//text())', [ '_s' ] }, sub { d $abcd, 'string(//comment())', [ '_R' ] }, sub { d $abcd, 'string(//processing-instruction())', [ '_rr' ], }, sub { d $ab, 'string(a/b)', [ '_b' ] }, sub { d $ab, 'string(b)', [ '_' ] }, sub { d $abcd, 'string(a/b/c/d)', [ '_v' ], }, sub { d $abcd, 'string(//d)', [ '_v' ], }, sub { d $abcd, 'string(//@id)', [ '_1' ], }, sub { d $abcd, 'concat(//@id, "")', [ '_1' ], }, sub { d $a, 'boolean(a)', [ '' ], 'true' }, sub { d $a, 'boolean(b)', [ '' ], 'false' }, sub { d $abcd, 'boolean(a/b)', [ '' ], 'true' }, sub { d $abcd, 'boolean(a/b/c/d)', [ '' ], 'true' }, sub { d $abcd, 'boolean(//@id)', [ '' ], 'true' }, sub { d $a, 'not(a)', [ '' ], 'false' }, sub { d $abcd, 'not(a/b/c/d)', [ '' ], 'false' }, sub { d $a, 'not(b)', [ '' ], 'true' }, sub { d $abc123, 'number(/a)', [ '' ], '123321' }, sub { d $abc123, 'number(/a/b)', [ '' ], '2332' }, sub { d $abc123, 'number(//c)', [ '_3' ], }, sub { d $abc123, 'number(//@id)', [ '_10' ], }, sub { d $abc123, '- //@id', [ '_-10' ], }, ## Multiple precursors sub { d $ab, 'concat( //@id, //@id )', [ '_11' ], }, sub { d $ab, 'concat( //@id, //@name )', [ '_1joe' ], }, sub { d $ab, 'string(a | a/b)', [ '_bA' ] }, sub { d $ab, 'string(c | a/b)', [ '_b' ] }, sub { d $ab, 'concat( string(a), ":", string(a) )', [ '_bA:bA' ], }, sub { d $ab, 'concat( string(a), ":", string(a/b) )', [ '_bA:b' ], }, sub { d $ab, 'concat( string(a), ":", string(@id) )', [ '_bA:' ], }, sub { d $ab, 'concat( string(a), ":", string(a/b/@id) )', [ '_bA:1' ], }, sub { d $ab, 'concat( string(a), ":", string(a//@id) )', [ '_bA:1' ], }, ## Variable references sub { d $var, 'concat( $foo, "!" )', [ '' ], 'true!' }, ## Nested rules sub { d $abcd, [ 'a' => [ 'b' ] ], [ 'b' ] }, sub { d $abcd, [ 'a[b]' => [ 'b' ] ], [ 'b' ] }, sub { d $abcd, [ a => [ 'b', b => [ 'c' ] ] ], [ 'b', 'c' ] }, sub { d $abcd, [ a => [ b => [ c => [ "string( d )" ] ] ] ], [ 'c_v' ] }, sub { d $abcd, [ 'a/b' => [ c => [ "string( d )" ] ] ], [ 'c_v' ] }, sub { d $abcd, [ 'a/b/c' => [ "string( d )" ] ], [ 'c_v' ] }, sub { d $abcdBcd, [ 'a/b/c' => [ "string( d )" ] ], [ 'c_1' ] }, sub { d $abcdBcd, [ 'a/b/c|a/B/c' => [ "string( d )" ] ], [ 'c_1', 'c_3' ] }, ## Postponement sub { d $aaaabaa, '//a[b]', [ 'a' ] }, sub { d $aaaabaa, '//a[b]/a', [ 'a', 'a' ] }, sub { d $aaaabaa, '//a[b]//a', [ 'a', 'a', 'a' ] }, sub { d $aaaaaab, '//a[b]', [ 'a' ] }, sub { d $aaaaaab, '//a[b]/a', [ 'a', 'a' ] }, sub { d $aaaaaab, '//a[b]//a', [ 'a', 'a', 'a' ] }, sub { d $aaacb, '//a[b]//a[c]//a', [ 'a' ] }, sub { d $aaaacb, '//a[b]//a[c]//a', [ 'a', 'a' ] }, ## SAX axes sub { d $ab, '/end-document::*', [ '' ] }, sub { d $ab, '/a/end-element::b', [ 'b' ] }, sub { d $ab, '/a/end::b', [ 'b' ] }, sub { d $ab, '/a[b]/end-element::b', [ 'b' ] }, sub { d $abcdBcd, '/a[b]/end-element::b', [ 'b' ] }, sub { d $abcdBcd, '/a[b]/end-element::B', [ 'B' ] }, sub { d $abcdBcd, '/a[B]/end-element::b', [ 'b' ] }, sub { d $ab, '/a/start-element::b', [ 'b' ] }, sub { d $ab, '/a/start::b', [ 'b' ] }, sub { d $ab, '/start-document::*', [ '' ] }, ## Namespace tests sub { d $ns, 'local-name(a)', [ '_a' ], }, sub { d $ns, 'local-name(a)', [ '_a' ], { Namespaces => { "" => "default-ns", bar => "foo-ns", }, } }, sub { d $ns, 'local-name(bar:a)', [ '_a' ], { Namespaces => { bar => "default-ns", }, } }, sub { d $ns, 'local-name(//b)', [ '_b' ], { Namespaces => { "" => "foo-ns", }, } }, sub { d $ns, 'local-name(//bar:*)', [ '_a' ], { Namespaces => { "" => "default-ns", "bar" => "default-ns", }, } }, sub { d $ns, 'local-name(//bar:*)', [ '_b' ], { Namespaces => { "bar" => "foo-ns", }, } }, ## ## Some more complex expressions ## sub { d $ab, 'string( //b )', ['_b'] }, sub { d $ab, 'string( //* )', ['_bA'] }, sub { d $ab, '//*[*]', ['a'] }, sub { d $ab, '//*[not(*)]', ['b'] }, #sub { d $ab, [ "//*[not(*)]" => [ "string()" ] ], [ 'b_b' ] }, ## TODO ); plan tests => 2 * @tests; for ( @tests ) { $fold_constants = 0; $_->(); $fold_constants = 1; $_->(); } ## This quick little buffering filter is used to save us the overhead ## of a parse for each test. This saves me sanity (since I run the test ## suite a lot), allows me to see which tests are noticably slower in ## case something pathalogical happens, and keeps admins from getting the ## impression that this is a slow package based on test suite speed. package QB; use vars qw( $AUTOLOAD ); use File::Basename; sub new { my $self = bless [], shift; my ( $name, $doc ) = @_; my $cache_fn = basename( $0 ) . ".cache.$name"; if ( -e $cache_fn && -M $cache_fn < -M $0 ) { my $old_self = do $cache_fn; return $old_self if defined $old_self; warn "$!$@"; unlink $cache_fn; } require XML::SAX::PurePerl; ## Cannot use ParserFactory; LibXML 1.31 is broken. require Data::Dumper; my $p = XML::SAX::PurePerl->new( Handler => $self ); $p->parse_string( $doc ); if ( open F, ">$cache_fn" ) { local $Data::Dumper::Terse; $Data::Dumper::Terse = 1; print F Data::Dumper::Dumper( $self ); close F; } return $self; } sub DESTROY; sub AUTOLOAD { my $self = shift; $AUTOLOAD =~ s/.*://; if ( $AUTOLOAD eq "start_element" ) { ## Older (and mebbe newer :) X::S::PurePerls reuse the same ## hash in end_element but delete the Attributes, so we need ## to copy. And I can't copy everything because some other ## overly magical thing dies, haven't tracked down beyond seeing ## signs that it's XML::SAX::DocumentLocator::NEXTKEY(/usr/local/lib/perl5/site_perl/5.6.1/XML/SAX/DocumentLocator.pm:72) ## but I hear that's fixed in CVS :). push @$self, [ $AUTOLOAD, [ { %{$_[0]} } ] ]; } else { push @$self, [ $AUTOLOAD, [ $_[0] ] ]; } } sub playback { my $self = shift; my $h = shift; for ( @$self ) { my $m = $_->[0]; no strict "refs"; $h->$m( @{$_->[1]} ); } }