RSS Git Download  Clone
Raw Blame History
package LIMS::Controller::Roles::Aspell;

use Moose::Role;

use Data::Dumper;
use Text::Aspell;
use Path::Tiny;

has speller => ( is => 'ro', isa => 'Text::Aspell', lazy_build => 1 );
has id_generator => ( is => 'rw', isa => 'Str' );

has seen => (
    is        => 'ro',
    isa       => 'HashRef[Str]',
    default   => sub { {} },
	lazy      => 1,
    traits    => ['Hash'],
    handles   => {
        has_seen => 'get',
		set_seen => 'set',
    },
);

has words => (
    is        => 'ro',
    isa       => 'HashRef[Str]',
    default   => sub { {} },
	lazy      => 1,
	traits  => ['Hash'],
	handles => {
		has_word => 'get',
		set_word => 'set',
	},
);

has word_data => (
    is        => 'ro',
    isa       => 'ArrayRef[HashRef]',
    default   => sub { [] },
	lazy      => 1,
	traits  => ['Array'],
	handles => {
		add_word_data   => 'push',
		all_word_data   => 'elements',
        clear_word_data => 'clear',
	},
);

#-------------------------------------------------------------------------------
# idea borrowed from http://sedition.com/perl/cgi-javascript-spell.html
sub do_spellcheck {
	my ($self, $fieldname) = @_;

    my $speller = $self->speller; # $self->debug($speller->list_dictionaries);

	my $text = $self->query->param($fieldname); # warn $text;

	WORD:
    while ( $text =~ /(['\w]+)/g ) { # add apos back
        my $word = $1;

		# skip numbers, CD4, etc:
		next WORD if $self->_is_unsupported($word);
		# skip if already seen:
        next WORD if $self->has_seen($word);

		# skip if word recognised in dictionary(s):
        unless ( $speller->check($word) ) { # warn $word;
			# add unrecognised word to 'words' collection:
			$self->set_word($word => 1);

			# get unique id for div:
			my $id = $self->id_generator; # warn $id;
			my $div_id = join '_', $fieldname, $id; # warn $div_id;

            my @suggestions = map {
				{
					escaped => quotemeta($_), # so apostrophied words work OK
					display => $_, # for <a href> display
				}
			} $speller->suggest($word);

            # eg, to restrict to 5:
            # @suggestions = splice(@sug,0,6) if @suggestions > 6;

			my %data = (
				id 			=> $div_id,
				word 		=> quotemeta($word),
				fieldname   => $fieldname,
				suggestions => \@suggestions,
			);
			$self->add_word_data(\%data);

			# increment id_generator for next time:
			$self->id_generator(++$id);
        }
		# add word to 'seen' collection:
		$self->set_seen($word => 1);
    }

	my @data = $self->all_word_data;
	# reset for any more textareas:
	$self->clear_word_data;

	return \@data;
}

#-------------------------------------------------------------------------------
sub colourise_text {
	my ($self, $fieldname) = @_;

	my $text = $self->query->param($fieldname); # warn $text;

	my $q = $self->query;

	my @words;
    while ( $text =~ /(['\w]+)/g ) { # add apos back
        my $word = $1;

		push @words, $self->has_word($word)
			? $q->span({-class=>'unrecognised'}, $word) # highlight it
			: $word;
	}

	return \@words;
}

#-------------------------------------------------------------------------------
sub get_speller_tempfile {
    my $self = shift;
    # speller is handed from add_new_words(), not from check_speller_words():
    my $speller = shift || $self->speller(); # $speller->print_config;

    my $file = sprintf '.aspell.%s.temp', $self->cfg('settings')->{_centre};
    my $dir  = $speller->get_option('home-dir'); # warn $dir;
    my $src  = path($dir, $file)->realpath; # warn $src;
    return $src;
}

#-------------------------------------------------------------------------------
sub _build_speller {
	my $self = shift;

    my $path = $self->cfg->{path_to_www_docs}; # warn $path;
    my $ctr  = $self->cfg('settings')->{_centre}; # warn $ctr;

    my $speller = Text::Aspell->new;

	# *.pws & *.prepl must be read/write by the webserver (eg 664 raj:www-data):
	# location of personal dicts:
    my $personal = sprintf '.aspell.%s.pws', $ctr; # warn $personal;
	my $home_dir = path($path, 'speller', 'dicts')->realpath; # warn $home_dir;
    my $medical  = path($home_dir, 'en-medical.rws')->realpath; # warn $medical;

    my %opts = (
        lang       => 'en_GB',
        personal   => $personal,
        'sug-mode' => 'fast',
        'home-dir' => $home_dir,
        'extra-dicts' => $medical, # needs manually rebuilding (see README.txt)
    ); # warn Dumper \%opts;

    $speller->set_option( $_ => $opts{$_} ) for keys %opts;
        # $speller->print_config; # !! doesn't report extra-dicts !!
    return $speller;
}

#-------------------------------------------------------------------------------
sub _is_unsupported {
	my $self = shift;
	my $word = shift;

	return 1 if $word =~ /\A(CD?)\d+\Z/; # CD4 or just numbers
}

1;