Subject: [PATCH] When spell checking large texts determine what's more convenient: to look every word in the text for spelling mistakes or to look for known spelling mistakes in the text.

From: Raphael Geissert <[email-masked]>
Date: Fri, 6 Feb 2009 15:34:03 -0600

This should speed up checking large texts, with the only, minor, consecuence
being that only the first match of a spelling mistake is found and warned
about; but since the line numbers are not printed it is not big deal.

Additionally move some regular expressions and other operations so that they
are performed once for all the text, instead of doing it once on every word.
---
 lib/Spelling.pm |   70 +++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 48 insertions(+), 22 deletions(-)

diff --git a/lib/Spelling.pm b/lib/Spelling.pm
index 2fc5776..9262f83 100644
--- a/lib/Spelling.pm
+++ b/lib/Spelling.pm
@@ -316,6 +316,8 @@ our %CORRECTIONS = qw(
 # The format above doesn't allow spaces.
 $CORRECTIONS{'alot'} = 'a lot';
 
+our @CORRECTIONS = keys(%CORRECTIONS);
+
 # Picky corrections, applied before lowercasing the word.  These are only
 # applied to things known to be entirely English text, such as package
 # descriptions, and should not be applied to files that may contain
@@ -394,6 +396,8 @@ $CORRECTIONS_CASE{'TeX-live'} = 'TeX Live';
 $CORRECTIONS_CASE{'texlive'} = 'TeX Live';
 $CORRECTIONS_CASE{'tex-live'} = 'TeX Live';
 
+our @CORRECTIONS_CASE = keys(%CORRECTIONS_CASE);
+
 # -----------------------------------
 
 sub _tag {
@@ -408,24 +412,36 @@ sub spelling_check {
     my ($tag, $text, $filename) = @_;
     return unless $text;
 
-    for my $word (split(/\s+/, $text)) {
-        $word = lc $word;
+    $text = lc $text;
 
-        # Try deleting the non-alphabetic parts from the word.  Treat
-        # apostrophes specially: only delete them if they occur at the
-        # beginning or end of the word.
-        #
-        # FIXME: Should do something that's aware of Unicode character
-        # classes rather than only handling ISO 8859-15 characters.
-        $word =~ s/(^\')|[^\w\xc0-\xd6\xd8-\xf6\xf8-\xff\'-]+|(\'\z)//g;
-        if (exists $CORRECTIONS{$word}) {
-            _tag($tag, $filename, $word, $CORRECTIONS{$word});
-        }
+    # Special case for correcting a multi-word string.
+    if ($text =~ m,Debian/GNU\s+Linux,i) {
+        _tag($tag, $filename, $&, "Debian GNU/Linux");
     }
 
-    # Special case for correcting a multi-word string.
-    if ($text =~ m,Debian/GNU\s+Linux,) {
-        _tag($tag, $filename, "Debian/GNU Linux", "Debian GNU/Linux");
+    # Try deleting the non-alphabetic parts from the text.  Treat
+    # apostrophes specially: only delete them if they occur at the
+    # beginning or end of the word.
+    #
+    # FIXME: Should do something that's aware of Unicode character
+    # classes rather than only handling ISO 8859-15 characters.
+    $text =~ s/(\s\')|[^\w\xc0-\xd6\xd8-\xf6\xf8-\xff\'\s-]+|(\'\s)//g;
+
+    # Check whether we should check every word in the text or
+    # better look for every word we know about in the text.
+    # Use 10 as the average word length in %CORRECTIONS
+    if (length($text) < $#CORRECTIONS * 10) {
+	for my $word (split(/\s+/, $text)) {
+	    if (exists $CORRECTIONS{$word}) {
+		_tag($tag, $filename, $word, $CORRECTIONS{$word});
+	    }
+	}
+    } else {
+	for my $word (@CORRECTIONS) {
+	    my $rword = qr<$word>;
+	    _tag($tag, $filename, $word, $CORRECTIONS{$word})
+		if ($text =~ m,(^|\s)$rword(\s|$),);
+	}
     }
 }
 
@@ -441,19 +457,29 @@ sub spelling_check_picky {
     # Check this first in case it's contained in square brackets and
     # removed below.
     if ($text =~ m,meta\s+package,) {
-        _tag($tag, $filename, "meta package", "metapackage");
+        _tag($tag, $filename, $&, "metapackage");
     }
 
     # Exclude text enclosed in square brackets as it could be a package list
     # or similar which may legitimately contain lower-cased versions of
     # the words.
     $text =~ s/\[.+?\]//sg;
-    for my $word (split(/\s+/, $text)) {
-        $word =~ s/^\(|[).,?!:;]+$//g;
-        if (exists $CORRECTIONS_CASE{$word}) {
-            _tag($tag, $filename, $word, $CORRECTIONS_CASE{$word});
-            next;
-        }
+
+    # Check whether we should check every word in the text or
+    # better look for every word we know about in the text.
+    # Use 7 as the average word length in %CORRECTIONS_CASE
+    if (length($text) < $#CORRECTIONS_CASE * 7) {
+	for my $word (split(/\s+/, $text)) {
+	    $word =~ s/^\(|[).,?!:;]+$//g;
+	    if (exists $CORRECTIONS_CASE{$word}) {
+		_tag($tag, $filename, $word, $CORRECTIONS_CASE{$word});
+	    }
+	}
+    } else {
+	for my $word (@CORRECTIONS_CASE) {
+	    _tag($tag, $filename, $word, $CORRECTIONS_CASE{$word})
+		if ($text =~ m,(^|\s)$word(\s|$),);
+	}
     }
 }