File Coverage

blib/lib/HTML/Feature/Engine/TagStructure.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package HTML::Feature::Engine::TagStructure;
2 7     7   1045 use strict;
  7         14  
  7         258  
3 7     7   38 use warnings;
  7         17  
  7         189  
4 7     7   6791 use Statistics::Lite qw(statshash);
  7         11317  
  7         573  
5 7     7   20067 use HTML::TreeBuilder::LibXML;
  0            
  0            
6             use HTML::Feature::Result;
7             use base qw(HTML::Feature::Base);
8              
9             sub run {
10             my $self = shift;
11             my $html_ref = shift;
12             my $url = shift;
13             my $result = shift;
14             my $c = $self->context;
15             $self->_tag_cleaning($html_ref);
16             my $tree = HTML::TreeBuilder::LibXML->new;
17             $tree->parse($$html_ref);
18             $tree->eof;
19             my $data;
20              
21             if ( !$result->title ) {
22             if ( my $title = $tree->findvalue('//title') ) {
23             $result->title($title);
24             }
25             }
26             if ( !$result->desc ) {
27             if ( my $desc =
28             $tree->look_down( _tag => 'meta', name => 'description' ) )
29             {
30             my $string = $desc->attr('content');
31             $string =~ s{
}{}xms;
32             $result->desc($string);
33             }
34             }
35             my $i = 0;
36             my @ratio;
37             my @depth;
38             my @order;
39             for my $node (
40             $tree->look_down( sub { 1 if $_->tag =~ /body|center|td|div/i } ) )
41             {
42             my $html_length = bytes::length( $node->as_HTML );
43             my $text = $node->as_text;
44             my $text_length = bytes::length($text);
45             my $text_ration = $text_length / ( $html_length + 0.001 );
46              
47             next
48             if ( $c->{max_bytes}
49             and $c->{max_bytes} =~ /^[\d]+$/
50             && $text_length > $c->{max_bytes} );
51             next
52             if ( $c->{min_bytes}
53             and $c->{min_bytes} =~ /^[\d]+$/
54             and $text_length < $c->{min_bytes} );
55              
56             my $a_count = 0;
57             my $a_length = 0;
58             my $option_count = 0;
59             my $option_length = 0;
60             my %node_hash = (
61             text => '',
62             a_length => 0,
63             short_string_length => 0
64             );
65             $self->_walk_tree( $node, \%node_hash );
66             $node_hash{a_length} ||= 0;
67             $node_hash{option_length} ||= 0;
68             $node_hash{short_string_length} ||= 0;
69             $node_hash{text} ||= $text;
70             $data->[$i]->{text} = $node_hash{text};
71             push(
72             @ratio,
73             (
74             $text_length -
75             $node_hash{a_length} -
76             $node_hash{option_length} -
77             $node_hash{short_string_length}
78             ) * $text_ration
79             );
80             my $depth;
81              
82             for ( $node->{node}->nodePath =~ /(\/)/g ) {
83             $depth++;
84             }
85             $depth -= 2;
86             push( @depth, $depth );
87             $data->[$i]->{element} = $node;
88             $i++;
89             }
90             for ( 0 .. $i ) {
91             push( @order, log( $i - $_ + 1 ) );
92             }
93             my %ratio = statshash @ratio;
94             my %depth = statshash @depth;
95             my %order = statshash @order;
96             $tree->delete() unless $c->{element_flag}; # avoid memory leak
97             my @sorted = sort { $data->[$b]->{score} <=> $data->[$a]->{score} }
98             map {
99             my $ratio_std =
100             ( ( $ratio[$_] || 0 ) - ( $ratio{mean} || 0 ) ) /
101             ( $ratio{stddev} + 0.001 );
102             my $depth_std =
103             ( ( $depth[$_] || 0 ) - ( $depth{mean} || 0 ) ) /
104             ( $depth{stddev} + 0.001 );
105             my $order_std =
106             ( ( $order[$_] || 0 ) - ( $order{mean} || 0 ) ) /
107             ( $order{stddev} + 0.001 );
108             $data->[$_]->{score} = $ratio_std + $depth_std + $order_std;
109             $_;
110             } ( 0 .. $i );
111             $data->[ $sorted[0] ]->{text}
112             and $data->[ $sorted[0] ]->{text} =~ s/ $//s;
113             $result->text( $data->[ $sorted[0] ]->{text} );
114             if ( $c->{element_flag} ) {
115             $result->root($tree);
116             $result->element( $data->[ $sorted[0] ]->{element} );
117             }
118             if ( $result->text ) {
119             $result->{matched_engine} = 'TagStructure';
120             }
121             $tree->delete;
122             return $result;
123             }
124              
125             sub _walk_tree {
126             my $self = shift;
127             my $node = shift;
128             my $node_hash_ref = shift;
129             if ( ref $node ) {
130             for (qw/a option dt th/) {
131             if ( $node->tag eq $_ ) {
132             $node_hash_ref->{a_length} += bytes::length( $node->as_text );
133             }
134             }
135             if ( bytes::length( $node->as_text ) < 20 ) {
136             $node_hash_ref->{short_string_length} +=
137             bytes::length( $node->as_text );
138             }
139             $self->_walk_tree( $_, $node_hash_ref )
140             for $node->findnodes('child::*');
141             }
142             }
143              
144             sub _tag_cleaning {
145             my $self = shift;
146             my $html_ref = shift;
147             ## preprocessing
148             $$html_ref =~ s{}{}xmsg;
149             $$html_ref =~ s{]*>.*?<\/script>}{}xmgs;
150             $$html_ref =~ s{ }{ }xmg;
151             $$html_ref =~ s{"}{\'}xmg;
152             $$html_ref =~ s{\r\n}{\n}xmg;
153             $$html_ref =~ s{^\s*(.+)$}{$1}xmg;
154             $$html_ref =~ s{^\t*(.+)$}{$1}xmg;
155             ## control code ( 0x00 - 0x1F, and 0x7F on ascii)
156             for ( 0 .. 31 ) {
157             next if $_ == 10; # without NL(New Line)
158             my $control_code = '\x' . sprintf( "%x", $_ );
159             $$html_ref =~ s{$control_code}{}xmg;
160             }
161             $$html_ref =~ s{\x7f}{}xmg;
162             }
163             1;
164             __END__