Anonymous avatar Anonymous committed 94bff80

Modified Files:
lib/XML/LibXML/Iterator.pm lib/XML/LibXML/NodeList.pm
+ node filter support

Comments (0)

Files changed (2)

lib/XML/LibXML/Iterator.pm

   },
 ;
 
+use XML::NodeFilter qw(:results);
+
 sub new {
     my $class = shift;
     my $node  = shift;
     $self->first;
     $self->{ITERATOR} = \&default_iterator;
 
+    $self->{FILTERS} = [];
+
     return $self;
 }
 
     }
 }
 
+sub set_filter {
+    my $self = shift;
+    $self->{FILTERS} = [ @_ ];
+}
+
+sub add_filter {
+    my $self = shift;
+    push @{$self->{FILTERS}}, @_;
+}
+
 sub current  { return $_[0]->{CURRENT}; }
 sub index    { return $_[0]->{INDEX}; }
 
 sub next     {
     my $self = shift;
-    my $node = $self->{ITERATOR}->( $self, 1 );
+    my @filters = @{$self->{FILTERS}};
+    my $node = undef;
+    my $fv = FILTER_SKIP;
+    unless ( scalar @filters > 0 ) {
+        $fv = FILTER_DECLINED;
+    }
+    while ( 1 ) {
+        $node = $self->{ITERATOR}->( $self, 1 );
+        last unless defined $node;
+        foreach my $f ( @filters ) {
+            $fv = $f->accept_node( $node );
+            last if $fv;
+        }
+        last if $fv == FILTER_ACCEPT or $fv == FILTER_DECLINED;
+    }
 
     if ( defined $node ) {
         $self->{CURRENT} = $node;
 
 sub previous {
     my $self = shift;
-
-    my $node = $self->{ITERATOR}->( $self, -1 );
+    my @filters = @{$self->{FILTERS}};
+    my $node = undef;
+    my $fv = FILTER_SKIP;
+    unless ( scalar @filters > 0 ) {
+        $fv = FILTER_DECLINED;
+    }
+    while ( 1 ) {
+        $node = $self->{ITERATOR}->( $self, -1 );
+        last unless defined $node;
+        foreach my $f ( @filters ) {
+            $fv = $f->accept_node( $node );
+            last if $fv;
+        }
+        last if $fv == FILTER_ACCEPT or $fv == FILTER_DECLINED;
+    }
 
     if ( defined $node ) {
         $self->{CURRENT} = $node;
     if ( scalar @_ ) {
         $self->{FIRST} = shift;
     }
+
     $self->{CURRENT} = $self->{FIRST};
+
+    # this logic is required if the node is not allowed to be shown
+    my @filters = @{$self->{FILTERS}||[]};
+    my $fv = FILTER_DECLINED;
+
+    foreach my $f ( @filters ) {
+        $fv = $f->accept_node( $self->{CURRENT} );
+        last if $fv;
+    }
+
+    $fv ||= FILTER_ACCEPT;
+
+    unless ( $fv == FILTER_ACCEPT ) {
+        return undef;
+    }
+
     $self->{INDEX}   = 0;
     return $self->current;
 }
 of reaching the iteration boundaries. That means it is not possible
 to iterate past the last element or before the first one.
 
+=head2 Node Filters
+
+XML::LibXML::Iterator accepts XML::NodeFilters to limit the nodes made
+available to the caller. Any nodefilter applied to
+XML::LibXML::Iterator will test if a node returned by the iteration
+function is visible to the caller.
+
+Different to the DOM Traversal Specification, XML::LibXML::Iterator
+allows filter stacks. This means it is possible to apply more than a
+single node filter to your node iterator.
+
 =head2 Complex Iterations
 
 By default XML::LibXML::Iterator will access all nodes of a given DOM
 
   node -> node's childnodes -> node's next sibling
 
-This is great for a wide range of scripts. Nevertheless this is
-limited for some applications. XML::LibXML::Iterator allows to change
-that behaviour. This is done by resetting XML::LibXML::Iterator's
-iterator function. By using the method iterator_function() to override
-the default iterator function, it is possible to implement iterations
+In combination with XML::Nodefilter this is best for a wide range of
+scripts and applications. Nevertheless this is still to restrictive
+for some applications. XML::LibXML::Iterator allows to change that
+behaviour. This is done by resetting XML::LibXML::Iterator's iterator
+function. By using the method iterator_function() to override the
+default iterator function, it is possible to implement iterations
 based on any iteration rule imaginable.
 
 A valid iterator function has to take two parameters: As the first
 
 =item iterator_function($funcion_ref)
 
+=item set_filter(@filter_list)
+
+=item add_filter(@filter_list)
+
 =item iterate($function_ref)
 
 =back
 
+=head1 SEE ALSO
 
-XML::LibXML::Iterator knows two types of callback. One is knows as the
-iterator function, the other is used by iterate(). The first function
-will be called for each call of next() or previous(). It is used to
-find out about the next node recognized by the iterator.
+L<XML::LibXML::Node>, L<XML::NodeFilter>
 
+=head1 AUTHOR
 
-The iterators iterate() function will take a function reference that
-takes as well two parameters. The first parameter is again the
-iterator object. The second parameter is the node to operate on. The
-iterate function can do any operation on the node as
-prefered. Appending new nodes or removing the current node will not
-confuse the iteration process: The iterator preloads the next object
-before calling the iteration function. Thus the Iterator will not find
-nodes appended by the iteration function.
+Christian Glahn, E<lt>christian.glahn@uibk.ac.atE<gt>
+
+=head1 COPYRIGHT
+
+(c) 2002, Christian Glahn. All rights reserved.
+
+This package is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.

lib/XML/LibXML/NodeList.pm

 package XML::LibXML::NodeList::Iterator;
 
 use strict;
+use XML::NodeFilter qw(:results);
 
 use overload
   '++' => sub { $_[0]->next;     $_[0]; },
     if ( defined $list ) {
         $self = bless [
                        $list,
-                       0
+                       0,
+                       [],
                       ], $class;
     }
+
     return $self;
 }
 
-sub first    { $_[0][1]=0; return $_[0][0][0]; }
-sub last     { $_[0][1]=scalar(@{$_[0][0]})-1; return $_[0][0][-1]; }
+sub set_filter {
+    my $self = shift;
+    $self->[2] = [ @_ ];
+}
+
+sub add_filter {
+    my $self = shift;
+    push @{$self->[2]}, @_;
+}
+
+# helper function.
+sub accept_node {
+    foreach ( @{$_[0][2]} ) {
+        my $r = $_->accept_node($_[1]);
+        return $r if $r;
+    }
+    # no filters or all decline ...
+    return FILTER_ACCEPT;
+}
+
+sub first    { $_[0][1]=0;
+               my $s = scalar(@{$_[0][0]});
+               while ( $_[0][1] < $s ) {
+                   last if $_[0]->accept_node($_[0][0][$_[0][1]]) == FILTER_ACCEPT;
+                   $_[0][1]++;
+               }
+               return undef if $_[0][1] == $s;
+               return $_[0][0][$_[0][1]]; }
+
+sub last     {
+    my $i = scalar(@{$_[0][0]})-1;
+    while($i >= 0){
+        if ( $_[0]->accept_node($_[0][0][$i] == FILTER_ACCEPT) ) {
+            $_[0][1] = $i;
+            last;
+        }
+        $i--;
+    }
+
+    if ( $i < 0 ) {
+        # this costs a lot, but is more safe
+        return $_[0]->first;
+    }
+    return $_[0][0][$i];
+}
+
 sub current  { return $_[0][0][$_[0][1]]; }
 sub index    { return $_[0][1]; }
 
     if ( (scalar @{$_[0][0]}) <= ($_[0][1] + 1)) {
         return undef;
     }
-    $_[0][1]++;
+    my $i = $_[0][1];
+    while ( 1 ) {
+        $i++;
+        return undef if $i >= scalar @{$_[0][0]};
+        if ( $_[0]->accept_node( $_[0][0]->[$i] ) == FILTER_ACCEPT ) {
+            $_[0][1] = $i;
+            last;
+        }
+    }
     return $_[0][0]->[$_[0][1]];
 }
 
     if ( $_[0][1] <= 0 ) {
         return undef;
     }
-    $_[0][1]--;
+    my $i = $_[0][1];
+    while ( 1 ) {
+        $i--;
+        return undef if $i < 0;
+        if ( $_[0]->accept_node( $_[0][0]->[$i] ) == FILTER_ACCEPT ) {
+            $_[0][1] = $i;
+            last;
+        }
+    }
     return $_[0][0][$_[0][1]];
 }
 
     return unless defined $funcref && ref( $funcref ) eq 'CODE';
     $self->[1] = -1;
     my $rv;
-    while ( <$self> ) {
+    while ( $self->next ) {
         $rv = $funcref->( $self, $_ );
     }
     return $rv;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.