#======================================================================= # Default Highlighting Code # # Context highlighting & deals with stemming, but not phrases or stopwords # # $Id: DefaultHighlight.pm,v 1.3 2003/07/23 00:45:16 whmoseley Exp $ #======================================================================= package SWISH::DefaultHighlight; use strict; sub new { my ( $class, $settings, $headers ) = @_; my $self = bless { settings=> $settings, headers => $headers, }, $class; if ( $self->header('stemming applied') =~ /^(?:1|yes)$/i ) { eval { require SWISH::Stemmer }; if ( $@ ) { warn('Stemmed index needs Stemmer.pm to highlight: ' . $@); } else { $self->{stemmer_function} = \&SWISH::Stemmer::SwishStem; } } $self->set_match_regexp; return $self; } sub header { my $self = shift; return '' unless ref $self->{headers} eq 'HASH'; return $self->{headers}{$_[0]} || ''; } #========================================================================== # Returns true IF prop was HTML escaped. sub highlight { my ( $self, $text_ref, $phrase_array, $prop_name ) = @_; my $wc_regexp = $self->{wc_regexp}; my $extract_regexp = $self->{extract_regexp}; my $match_regexp = $self->match_string( $phrase_array, $prop_name ); my $last = 0; my $settings = $self->{settings}; my $Show_Words = $settings->{show_words} || 10; my $Occurrences = $settings->{occurrences} || 5; my $Max_Words = $settings->{max_words} || 100; my $On = $settings->{highlight_on} || ''; my $Off = $settings->{highlight_off} || ''; my $on_flag = 'sw' . time . 'on'; my $off_flag = 'sw' . time . 'off'; my $stemmer_function = $self->{stemmer_function}; # Should really call unescapeHTML(), but then would need to escape from escaping. my @words = split /$wc_regexp/, $$text_ref; return unless @words; my @flags; $flags[$#words] = 0; # Extend array. my $occurrences = $Occurrences ; my $pos = 0; while ( $Show_Words && $pos <= $#words ) { # Check if the word is a swish word (ignoring begin and end chars) if ( $words[$pos] =~ /$extract_regexp/ ) { my ( $begin, $word, $end ) = ( $1, $2, $3 ); my $test = $stemmer_function ? $stemmer_function->($word) : lc $word; $test ||= lc $word; # Not check if word matches if ( $test =~ /$match_regexp/ ) { $words[$pos] = "$begin$on_flag$word$off_flag$end"; my $start = $pos - ($Show_Words-1)* 2; my $end = $pos + ($Show_Words-1)* 2; if ( $start < 0 ) { $end = $end - $start; $start = 0; } $end = $#words if $end > $#words; $flags[$_]++ for $start .. $end; # All done, and mark where to stop looking if ( $occurrences-- <= 0 ) { $last = $end; last; } } } $pos += 2; # Skip to next wordchar word } my $dotdotdot = ' ... '; my @output; my $printing; my $first = 1; my $some_printed; if ( $Show_Words && @words > 50 ) { # don't limit context if a small number of words for my $i ( 0 ..$#words ) { if ( $last && $i >= $last && $i < $#words ) { push @output, $dotdotdot; last; } if ( $flags[$i] ) { push @output, $dotdotdot if !$printing++ && !$first; push @output, $words[$i]; $some_printed++; } else { $printing = 0; } $first = 0; } } if ( !$some_printed ) { for my $i ( 0 .. $Max_Words ) { if ( $i > $#words ) { $printing++; last; } push @output, $words[$i]; } } push @output, $dotdotdot if !$printing; $$text_ref = join '', @output; my %entities = ( '&' => '&', '>' => '>', '<' => '<', '"' => '"', ); my %highlight = ( $on_flag => $On, $off_flag => $Off, ); $$text_ref =~ s/([&"<>])/$entities{$1}/ge; # " fix emacs $$text_ref =~ s/($on_flag|$off_flag)/$highlight{$1}/ge; return 1; # Means that prop was processed AND was html escaped. } #============================================ # Returns compiled regular expressions for matching # sub match_string { my ($self, $phrases, $prop_name ) = @_; # Already cached? return $self->{cache}{$prop_name} if $self->{cache}{$prop_name}; my $wc = quotemeta $self->header('wordcharacters'); # Yuck! $wc .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; # Warning: dependent on tolower used while indexing # Get all the unique words; my %words; for my $phrase ( @$phrases ) { $words{$_}++ for @$phrase; } my $match_string = join '|', map { substr( $_, -1, 1 ) eq '*' ? quotemeta( substr( $_, 0, -1) ) . "[$wc]*?" : quotemeta } keys %words; my $re = qr/^(?:$match_string)$/; $self->{cache}{$prop_name} = $re; return $re; } #============================================ # Returns compiled regular expressions for splitting the source text into "swish words" # # sub set_match_regexp { my $self = shift; my $ignoref = $self->header('ignorefirstchar'); my $ignorel = $self->header('ignorelastchar'); for ( $ignoref, $ignorel ) { if ( $_ ) { $_ = quotemeta; $_ = "([$_]*)"; } else { $_ = '()'; } } my $wc = quotemeta $self->header('wordcharacters'); $wc .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; # Warning: dependent on tolower used while indexing # Now, wait a minute. Look at this more, as I'd hope that making a # qr// go out of scope would release the compiled pattern. # doesn't really matter, as $wc probably never changes $self->{wc_regexp} = qr/([^$wc]+)/; # regexp for splitting into swish-words $self->{extract_regexp} = qr/^$ignoref([$wc]+?)$ignorel$/i; # regexp for extracting out the words to compare } 1;