Make identical text nodes reference identicalmaster
authorJiri Palecek <[email protected]>
Fri, 30 Mar 2012 21:02:26 +0000 (30 23:02 +0200)
committerJiri Palecek <jirka@debian>
Fri, 13 Apr 2012 12:59:07 +0000 (13 14:59 +0200)
 Note that this assumes that the tree is not manipulated
 afterwards. Since the code assumes this already (or breaks when the
 tree is manipulated, eg. by breaking _rank numbers), I don't think
 that is a big deal.

lib/HTML/TreeBuilder/XPath.pm
t/HTML-TreeBuilder-XPath.t

index ccc5f6b..aeda292 100644 (file)
@@ -116,20 +116,13 @@ sub find_xpath           { my( $elt, $path)= @_; return xp->find(
 sub matches              { my( $elt, $path)= @_; return xp->matches( $elt, $path, $elt);        }
 sub set_namespace        { my $elt= shift; xp->new->set_namespace( @_); }
 
-{
     my %roots;
-    sub DESTROY {
-        my $self = shift;
-        delete $roots{$self};
-        $self->SUPER::DESTROY if $self->can("SUPER::DESTROY");
-    }
 
     sub getParentNode
     { my $elt= shift;
       return $elt->{_parent} if $elt->{_parent};
       return $roots{$elt} //= bless { _root => $elt }, 'HTML::TreeBuilder::XPath::Root';
     }
-}
 
 sub getRootNode
   { my $elt= shift;
@@ -207,21 +200,35 @@ sub string_value
 # called on a parent, with a child as second argument and its rank as third
 # returns the child if it is already an element, or
 # a new HTML::TreeBuilder::XPath::Text element if it is a plain string
+    my %text_children;
+
+sub DESTROY {
+    my $self = shift;
+    delete $text_children{$self};
+    delete $roots{$self};
+    $self->SUPER::DESTROY if $self->can("SUPER::DESTROY");
+}
+
 sub _child_as_object
   { my( $elt, $elt_or_text, $rank)= @_;
     return undef unless( defined $elt_or_text);
     if( ! ref $elt_or_text)
       { # $elt_or_text is a string, turn it into a TextNode object
-        $elt_or_text= bless { _content => $elt_or_text, _parent => $elt, }, 
-                            'HTML::TreeBuilder::XPath::TextNode'
-                      ;
-        Scalar::Util::weaken($elt_or_text->{_parent});
+          if(!defined $text_children{$elt}{$rank} ||
+             ($elt_or_text= $text_children{$elt}{$rank}, $elt_or_text->{_rank} != $rank || $elt_or_text->{_parent} != $elt)) {
+              $elt_or_text = ($text_children{$elt}{$rank} =
+                              bless { _content => $elt_or_text, _parent => $elt, },
+                              'HTML::TreeBuilder::XPath::TextNode')
+                  ;
+              Scalar::Util::weaken($elt_or_text->{_parent});
+          }
       }
     if( ref $rank) { warn "rank is a ", ref( $rank), " elt_or_text is a ", ref( $elt_or_text); } 
     $elt_or_text->{_rank}= $rank; # used for sorting;
     return $elt_or_text;
   }
 
+
 sub toString { return shift->as_XML( @_); }
 
 # produces better looking XML
index cdf78b3..f81ac3a 100644 (file)
@@ -3,7 +3,7 @@
 
 #########################
 
-use Test::More tests => 31;
+use Test::More tests => 34;
 BEGIN { use_ok('HTML::TreeBuilder::XPath') };
 
 #########################
@@ -37,6 +37,10 @@ is( $html->findvalue( '//*[@id="foo"]/@id|//*[@id="foo"]/@class'), 'myspanfoo',
 is( $html->findvalue( '//*[@id="foo"]/@class|//*[@id="foo"]/@id'), 'myspanfoo', '2 atts on same element (unsorted)');
 
 is( $html->findvalue( 'count(/|/)' ), 1, 'count roots');
+is( $html->findvalue( 'count(/html/body/h1/text()|/html/body/h1/text())' ), 1, 'count identical text nodes');
+is( $html->findvalue( 'count(id("bq")/@id|id("bq")/@id)' ), 1, 'count identical attribute nodes');
+
+is( $html->findvalue( '//text()[contains(., "folks")]/preceding-sibling::*[1]' ), 'all', 'previous sibling on a text node');
 
 is( $html->findvalue( '//b'), 'boldall', '2 texts');
 is( join( '|', $html->findvalues( '//b')), 'bold|all', '2 texts with findvalues');