392 lines
8.8 KiB
Perl
392 lines
8.8 KiB
Perl
|
package PPI::Find;
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
PPI::Find - Object version of the Element->find method
|
||
|
|
||
|
=head1 SYNOPSIS
|
||
|
|
||
|
# Create the Find object
|
||
|
my $Find = PPI::Find->new( \&wanted );
|
||
|
|
||
|
# Return all matching Elements as a list
|
||
|
my @found = $Find->in( $Document );
|
||
|
|
||
|
# Can we find any matching Elements
|
||
|
if ( $Find->any_matches($Document) ) {
|
||
|
print "Found at least one matching Element";
|
||
|
}
|
||
|
|
||
|
# Use the object as an iterator
|
||
|
$Find->start($Document) or die "Failed to execute search";
|
||
|
while ( my $token = $Find->match ) {
|
||
|
...
|
||
|
}
|
||
|
|
||
|
=head1 DESCRIPTION
|
||
|
|
||
|
PPI::Find is the primary PDOM searching class in the core PPI package.
|
||
|
|
||
|
=head2 History
|
||
|
|
||
|
It became quite obvious during the development of PPI that many of the
|
||
|
modules that would be built on top of it were going to need large numbers
|
||
|
of saved, storable or easily creatable search objects that could be
|
||
|
reused a number of times.
|
||
|
|
||
|
Although the internal ->find method provides a basic ability to search,
|
||
|
it is by no means thorough. PPI::Find attempts to resolve this problem.
|
||
|
|
||
|
=head2 Structure and Style
|
||
|
|
||
|
PPI::Find provides a similar API to the popular L<File::Find::Rule>
|
||
|
module for file searching, but without the ability to assemble queries.
|
||
|
|
||
|
The implementation of a separate PPI::Find::Rule sub-class that does
|
||
|
provide this ability is left as an exercise for the reader.
|
||
|
|
||
|
=head2 The &wanted function
|
||
|
|
||
|
At the core of each PPI::Find object is a "wanted" function that is
|
||
|
passed a number of arguments and returns a value which controls the
|
||
|
flow of the search.
|
||
|
|
||
|
As the search executes, each Element will be passed to the wanted function
|
||
|
in depth-first order.
|
||
|
|
||
|
It will be provided with two arguments. The current Element to test as $_[0],
|
||
|
and the top-level Element of the search as $_[1].
|
||
|
|
||
|
The &wanted function is expected to return 1 (positive) if the Element
|
||
|
matches the condition, 0 (false) if it does not, and undef (undefined) if
|
||
|
the condition does not match, and the Find search should not descend to
|
||
|
any of the current Element's children.
|
||
|
|
||
|
Errors should be reported from the &wanted function via die, which will be
|
||
|
caught by the Find object and returned as an error.
|
||
|
|
||
|
=head1 METHODS
|
||
|
|
||
|
=cut
|
||
|
|
||
|
use strict;
|
||
|
use Params::Util qw{_INSTANCE};
|
||
|
|
||
|
our $VERSION = '1.270'; # VERSION
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#####################################################################
|
||
|
# Constructor
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head2 new &wanted
|
||
|
|
||
|
The C<new> constructor takes a single argument of the &wanted function,
|
||
|
as described above and creates a new search.
|
||
|
|
||
|
Returns a new PPI::Find object, or C<undef> if not passed a CODE reference.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
sub new {
|
||
|
my $class = ref $_[0] ? ref shift : shift;
|
||
|
my $wanted = ref $_[0] eq 'CODE' ? shift : return undef;
|
||
|
|
||
|
# Create the object
|
||
|
my $self = bless {
|
||
|
wanted => $wanted,
|
||
|
}, $class;
|
||
|
|
||
|
$self;
|
||
|
}
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head2 clone
|
||
|
|
||
|
The C<clone> method creates another instance of the same Find object.
|
||
|
|
||
|
The cloning is done safely, so if your existing Find object is in the
|
||
|
middle of an iteration, the cloned Find object will not also be in the
|
||
|
iteration and can be safely used independently.
|
||
|
|
||
|
Returns a duplicate PPI::Find object.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
sub clone {
|
||
|
my $self = ref $_[0] ? shift
|
||
|
: die "->clone can only be called as an object method";
|
||
|
my $class = ref $self;
|
||
|
|
||
|
# Create the object
|
||
|
my $clone = bless {
|
||
|
wanted => $self->{wanted},
|
||
|
}, $class;
|
||
|
|
||
|
$clone;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
####################################################################
|
||
|
# Search Execution Methods
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head2 in $Document [, array_ref => 1 ]
|
||
|
|
||
|
The C<in> method starts and completes a full run of the search.
|
||
|
|
||
|
It takes as argument a single L<PPI::Element> object which will
|
||
|
serve as the top of the search process.
|
||
|
|
||
|
Returns a list of PPI::Element objects that match the condition
|
||
|
described by the &wanted function, or the null list on error.
|
||
|
|
||
|
You should check the ->errstr method for any errors if you are
|
||
|
returned the null list, which may also mean simply that no Elements
|
||
|
were found that matched the condition.
|
||
|
|
||
|
Because of this need to explicitly check for errors, an alternative
|
||
|
return value mechanism is provide. If you pass the C<< array_ref => 1 >>
|
||
|
parameter to the method, it will return the list of matched Elements
|
||
|
as a reference to an ARRAY. The method will return false if no elements
|
||
|
were matched, or C<undef> on error.
|
||
|
|
||
|
The ->errstr method can still be used to get the error message as normal.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
sub in {
|
||
|
my $self = shift;
|
||
|
my $Element = shift;
|
||
|
my %params = @_;
|
||
|
delete $self->{errstr};
|
||
|
|
||
|
# Are we already acting as an iterator
|
||
|
if ( $self->{in} ) {
|
||
|
return $self->_error('->in called while another search is in progress', %params);
|
||
|
}
|
||
|
|
||
|
# Get the root element for the search
|
||
|
unless ( _INSTANCE($Element, 'PPI::Element') ) {
|
||
|
return $self->_error('->in was not passed a PPI::Element object', %params);
|
||
|
}
|
||
|
|
||
|
# Prepare the search
|
||
|
$self->{in} = $Element;
|
||
|
$self->{matches} = [];
|
||
|
|
||
|
# Execute the search
|
||
|
if ( !eval { $self->_execute; 1 } ) {
|
||
|
my $errstr = $@;
|
||
|
$errstr =~ s/\s+at\s+line\s+.+$//;
|
||
|
return $self->_error("Error while searching: $errstr", %params);
|
||
|
}
|
||
|
|
||
|
# Clean up and return
|
||
|
delete $self->{in};
|
||
|
if ( $params{array_ref} ) {
|
||
|
if ( @{$self->{matches}} ) {
|
||
|
return delete $self->{matches};
|
||
|
}
|
||
|
delete $self->{matches};
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
# Return as a list
|
||
|
my $matches = delete $self->{matches};
|
||
|
@$matches;
|
||
|
}
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head2 start $Element
|
||
|
|
||
|
The C<start> method lets the Find object act as an iterator. The method
|
||
|
is passed the parent PPI::Element object as for the C<in> method, but does
|
||
|
not accept any parameters.
|
||
|
|
||
|
To simplify error handling, the entire search is done at once, with the
|
||
|
results cached and provided as-requested.
|
||
|
|
||
|
Returns true if the search completes, and false on error.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
sub start {
|
||
|
my $self = shift;
|
||
|
my $Element = shift;
|
||
|
delete $self->{errstr};
|
||
|
|
||
|
# Are we already acting as an iterator
|
||
|
if ( $self->{in} ) {
|
||
|
return $self->_error('->in called while another search is in progress');
|
||
|
}
|
||
|
|
||
|
# Get the root element for the search
|
||
|
unless ( _INSTANCE($Element, 'PPI::Element') ) {
|
||
|
return $self->_error('->in was not passed a PPI::Element object');
|
||
|
}
|
||
|
|
||
|
# Prepare the search
|
||
|
$self->{in} = $Element;
|
||
|
$self->{matches} = [];
|
||
|
|
||
|
# Execute the search
|
||
|
if ( !eval { $self->_execute; 1 } ) {
|
||
|
my $errstr = $@;
|
||
|
$errstr =~ s/\s+at\s+line\s+.+$//;
|
||
|
$self->_error("Error while searching: $errstr");
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
1;
|
||
|
}
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head2 match
|
||
|
|
||
|
The C<match> method returns the next matching Element in the iteration.
|
||
|
|
||
|
Returns a PPI::Element object, or C<undef> if there are no remaining
|
||
|
Elements to be returned.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
sub match {
|
||
|
my $self = shift;
|
||
|
return undef unless $self->{matches};
|
||
|
|
||
|
# Fetch and return the next match
|
||
|
my $match = shift @{$self->{matches}};
|
||
|
return $match if $match;
|
||
|
|
||
|
$self->finish;
|
||
|
undef;
|
||
|
}
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head2 finish
|
||
|
|
||
|
The C<finish> method provides a mechanism to end iteration if you wish to
|
||
|
stop the iteration prematurely. It resets the Find object and allows it to
|
||
|
be safely reused.
|
||
|
|
||
|
A Find object will be automatically finished when C<match> returns false.
|
||
|
This means you should only need to call C<finish> when you stop
|
||
|
iterating early.
|
||
|
|
||
|
You may safely call this method even when not iterating and it will return
|
||
|
without failure.
|
||
|
|
||
|
Always returns true
|
||
|
|
||
|
=cut
|
||
|
|
||
|
sub finish {
|
||
|
my $self = shift;
|
||
|
delete $self->{in};
|
||
|
delete $self->{matches};
|
||
|
delete $self->{errstr};
|
||
|
1;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#####################################################################
|
||
|
# Support Methods and Error Handling
|
||
|
|
||
|
sub _execute {
|
||
|
my $self = shift;
|
||
|
my $wanted = $self->{wanted};
|
||
|
my @queue = ( $self->{in} );
|
||
|
|
||
|
# Pull entries off the queue and hand them off to the wanted function
|
||
|
while ( my $Element = shift @queue ) {
|
||
|
my $rv = &$wanted( $Element, $self->{in} );
|
||
|
|
||
|
# Add to the matches if returns true
|
||
|
push @{$self->{matches}}, $Element if $rv;
|
||
|
|
||
|
# Continue and don't descend if it returned undef
|
||
|
# or if it doesn't have children
|
||
|
next unless defined $rv;
|
||
|
next unless $Element->isa('PPI::Node');
|
||
|
|
||
|
# Add the children to the head of the queue
|
||
|
if ( $Element->isa('PPI::Structure') ) {
|
||
|
unshift @queue, $Element->finish if $Element->finish;
|
||
|
unshift @queue, $Element->children;
|
||
|
unshift @queue, $Element->start if $Element->start;
|
||
|
} else {
|
||
|
unshift @queue, $Element->children;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
1;
|
||
|
}
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head2 errstr
|
||
|
|
||
|
The C<errstr> method returns the error messages when a given PPI::Find
|
||
|
object fails any action.
|
||
|
|
||
|
Returns a string, or C<undef> if there is no error.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
sub errstr {
|
||
|
shift->{errstr};
|
||
|
}
|
||
|
|
||
|
sub _error {
|
||
|
my $self = shift;
|
||
|
$self->{errstr} = shift;
|
||
|
my %params = @_;
|
||
|
$params{array_ref} ? undef : ();
|
||
|
}
|
||
|
|
||
|
1;
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head1 TO DO
|
||
|
|
||
|
- Implement the L<PPI::Find::Rule> class
|
||
|
|
||
|
=head1 SUPPORT
|
||
|
|
||
|
See the L<support section|PPI/SUPPORT> in the main module.
|
||
|
|
||
|
=head1 AUTHOR
|
||
|
|
||
|
Adam Kennedy E<lt>adamk@cpan.orgE<gt>
|
||
|
|
||
|
=head1 COPYRIGHT
|
||
|
|
||
|
Copyright 2001 - 2011 Adam Kennedy.
|
||
|
|
||
|
This program is free software; you can redistribute
|
||
|
it and/or modify it under the same terms as Perl itself.
|
||
|
|
||
|
The full text of the license can be found in the
|
||
|
LICENSE file included with this module.
|
||
|
|
||
|
=cut
|