File Coverage

blib/lib/Test/XPath.pm
Criterion Covered Total %
statement 78 80 97.5
branch 25 30 83.3
condition 4 6 66.6
subroutine 19 19 100.0
pod 11 11 100.0
total 137 146 93.8


line stmt bran cond sub pod time code
1             package Test::XPath;
2              
3 7     7   621416 use strict;
  7         60  
  7         164  
4 7     7   116 use 5.6.2;
  7         20  
5 7     7   3851 use XML::LibXML '1.69';
  7         291610  
  7         42  
6 7     7   1301 use Test::Builder;
  7         15  
  7         5893  
7              
8             our $VERSION = '0.20';
9              
10             sub new {
11 18     18 1 4432 my ($class, %p) = @_;
12 18   66     81 my $doc = delete $p{doc} || _doc(\%p);
13 17         4872 my $xpc = XML::LibXML::XPathContext->new( $doc->documentElement );
14 17 100       74 if (my $ns = $p{xmlns}) {
15 3         8 while (my ($k, $v) = each %{ $ns }) {
  7         28  
16 4         21 $xpc->registerNs( $k => $v );
17             }
18             }
19             return bless {
20             xpc => $xpc,
21             node => $doc->documentElement,
22 17         57 filter => do {
23 17 100       46 if (my $f = $p{filter}) {
24 1 50       4 if (ref $f eq 'CODE') {
    50          
25 0         0 $f;
26             } elsif ($f eq 'css_selector') {
27 1     1   7 eval 'use HTML::Selector::XPath 0.06';
  1         17  
  1         26  
  1         57  
28 1 50       5 die 'Please install HTML::Selector::XPath to use CSS selectors'
29             if $@;
30             sub {
31 22     22   28 my $xpath = do {
32 22         62 my $xp = HTML::Selector::XPath->new(shift)->to_xpath(root => '//');
33 22 100 66     3906 if (eval { $_->isa(__PACKAGE__) } && $_->node ne $doc->documentElement) {
  22         215  
34             # Make it relative to the current node.
35 4         72 $xp =~ s{^///[*]}{.};
36             } else {
37             # Start from the top.
38 18         61 $xp =~ s{^///[*]}{};
39             }
40 22         48 $xp;
41             };
42 22         111 return $xpath;
43             }
44 1         20 } else {
45 0         0 die "Unknown filter: $f\n";
46             }
47             } else {
48 101     101   205 sub { shift },
49 16         188 }
50             },
51             }, $class;
52             }
53              
54             sub ok {
55 72     72 1 12121 my ($self, $xpath, $code, $desc) = @_;
56 72         144 my $xpc = $self->{xpc};
57 72         180 my $Test = Test::Builder->new;
58 72         467 $xpath = $self->{filter}->($xpath, $self);
59              
60             # Code and desc can be reversed, to support PerlX::MethodCallWithBlock.
61 72 50       481 ($code, $desc) = ($desc, $code) if ref $desc eq 'CODE';
62              
63 72 100       142 if (ref $code eq 'CODE') {
64             # Gonna do some recursive testing.
65             my @nodes = $xpc->findnodes($xpath, $self->{node})
66 15 50       56 or return $Test->ok(0, $desc);
67              
68             # Record the current test result.
69 15         949 my $ret = $Test->ok(1, $desc);
70              
71             # Call the code ref on each found node.
72 15         3468 local $_ = $self;
73 15         28 for my $node (@nodes) {
74 29         3417 local $self->{node} = $node;
75 29         62 $code->($self);
76             }
77 15         4077 return $ret;
78             } else {
79             # We're just testing for existence ($code is description).
80 57         163 $Test->ok( $xpc->exists($xpath, $self->{node}), $code);
81             }
82              
83             }
84              
85             sub not_ok {
86 4     4 1 12 my ($self, $xpath, $desc) = @_;
87 4         12 $xpath = $self->{filter}->($xpath);
88 4         13 my $Test = Test::Builder->new;
89 4         36 $Test->ok( !$self->{xpc}->exists($xpath, $self->{node}), $desc);
90             }
91              
92 26     26 1 4825 sub is { Test::Builder::new->is_eq( shift->find_value(shift), @_) }
93 5     5 1 632 sub isnt { Test::Builder::new->isnt_eq( shift->find_value(shift), @_) }
94 5     5 1 573 sub like { Test::Builder::new->like( shift->find_value(shift), @_) }
95 5     5 1 563 sub unlike { Test::Builder::new->unlike( shift->find_value(shift), @_) }
96 5     5 1 548 sub cmp_ok { Test::Builder::new->cmp_ok( shift->find_value(shift), @_) }
97              
98 4     4 1 89 sub node { shift->{node} }
99 1     1 1 27 sub xpc { shift->{xpc} }
100              
101             sub find_value {
102 47     47 1 321 my $self = shift;
103 47         110 $self->{xpc}->findvalue( $self->{filter}->(shift), $self->{node} );
104             }
105              
106             sub _doc {
107 17     17   35 my $p = shift;
108              
109             # Create and configure the parser.
110 17         92 my $parser = XML::LibXML->new;
111              
112             # Apply any parser options.
113 17 100       252 if (my $opts = $p->{options}) {
114 3         5 while (my ($k, $v) = each %{ $opts }) {
  10         149  
115 7 100       41 if (my $meth = $parser->can($k)) {
116 6         17 $parser->$meth($v)
117             } else {
118 1         4 $parser->set_option($k => $v);
119             }
120             }
121             }
122              
123             # Parse and return the document.
124 17 100       39 if ($p->{xml}) {
125             return $p->{is_html}
126             ? $parser->parse_html_string($p->{xml})
127 11 100       49 : $parser->parse_string($p->{xml});
128             }
129              
130 6 100       18 if ($p->{file}) {
131             return $p->{is_html}
132             ? $parser->parse_html_file($p->{file})
133 5 100       28 : $parser->parse_file($p->{file});
134             }
135              
136 1         4 require Carp;
137 1         138 Carp::croak(
138             'Test::XPath->new requires the "xml", "file", or "doc" parameter'
139             );
140             }
141              
142             # Add Test::XML::XPath compatibility?
143             # sub like_xpath($$;$) { __PACKAGE__->new( xml => shift )->ok( @_ ) }
144             # sub unlike_xpath($$;$) { __PACKAGE__->new( xml => shift )->not_ok( @_ ) }
145             # sub is_xpath($$$;$) { __PACKAGE__->new( xml => shift )->is( @_ ) }
146              
147             1;
148             __END__