Introduction

For those people that would like to have a spell checker in their tracker, well here is one using aspell.

The spell checker is only executed for the web interface and not for the mail gateway. I guess it's safe to assume that e-mail clients can do the spell check for us, so there's no need to check the spelling on issues received from the mail gateway.

The spell checker will check all words with exception of upper case words. Those will be skipped. In some cases we want to skip the spell check for all words. Therefor a check box can be checked. If checked, the spell check is skipped and all text is accepted as it is.
If a spell check is performed and spelling mistakes are found, the form will be returned with an enumeration of all misspelled words. If available, then suggestions will be given for those words to.
To make it easier to lookup those misspelled words, they will be marked with stars like *word*. Just go trough the text, correct the marked words (or write them in uppercase to skip them) and submit the issue again. And if you don't want the words to be skipped in uppercase, then just correct the words which need to be corrected, check the No spell checking check box and re-submit the form.

Requirements

To use this feature, the next software if required on your server:

Implementation

The spell checker is implemented in two action handlers and not in auditors. This ensures us that the spell checker will only execute for the web interface and not for the mail gateway.
The new action handlers only do the spell checker part. The rest of the handling is still done by the original roundup action handlers. This will keep our new action handlers plain and simple.

Both action handlers are almost the same. The difference is that one is for new issues while the other one is for updating an issue.
To use the action handlers, only minor changes are required. Regretfully the classic issue.item.html template uses a roundup method to put the submit button on the form together with the required hidden input fields that will tell roundup what to do, so we need to make some small changes to call the spell checker action handler in issue.item.html. Even so do we need to tell the action handler which form fields to check. This is done by adding an hidden input tag to the template. This hidden tag looks like:
   <input type="hidden" name="@aspell" value="<name>:<field>,...">
For the default classic template it will be:
   <input type="hidden" name="@aspell" value="title:title,@note:message">
The above line will perform spell checking on HTML form fields title and @note. If errors are found in these fields, the error message will refer to the fields title and message (like they are named on the page).

Best regards,
Marlon van den Berg


Source Code

These are the two new action handlers. Copy the content to a file and save it in folder '<tracker_home>/extensions':

    class AspellClass:
        def check(self):
            """\
            Check if a form input field has some spelling mistakes.

            If it has, it returns a tuple with a dictionary on the
            first tuple index and the marked up text on the second
            tuple index.

            The keys of the dictionary contain all the spelling
            mistakes. The items contain a tuple with suggestions
            for the word.

            The marked up text is the original input form field
            with '*' around the spelling mistakes.
            """

            def __inner(text):
                """\
                Does all the work.
                """

                import aspell
                import re

                # select english language
                sp = aspell.Speller('lang', 'en') 

                # split the text into list of words
                # (excluding punctuation marks)
                word_list = re.split('\W+', text)

                new_text = text

                errors = {}
                for word in word_list:
                    # skip empty words (not words at all) and
                    # skip upercase words
                    if word and not word.isupper():
                        word = word.lower()

                        # if it isn't checked before
                        if not errors.has_key(word):
                            # perform spellcheck on word
                            if not sp.check(word):
                                # word incorrect
                                suggestions = sp.suggest(word)

                                if suggestions:
                                    # put spelling mistake in error list with suggestions
                                    errors[word] = suggestions

                                    # put some markings around the word
                                    res = re.compile('\\b(%s)\\b'%word, re.IGNORECASE)
                                    new_text = res.sub('*\\1*', new_text)

                return (errors, new_text)

            if self.form.has_key('no_spell_check') \
            and self.form['no_spell_check'].value == '1':
                # no spell check requested
                return None

            errors = []

            if self.form.has_key('@aspell'):
                _aspell = '@aspell'
            elif self.form.has_key(':aspell'):
                _aspell = ':aspell'
            else:
                _aspell = None

            if _aspell:
                for field in self.form[_aspell].value.split(','):
                    form_name, nice_name = field.split(':')

                    if self.form.has_key(form_name):
                        new_errors, text = __inner(self.form[form_name].value)

                        if new_errors:
                            # update form text (will place the markings around
                            # the spelling mistakes)
                            self.form[form_name].value = text

                            if errors:
                                # more than one form field has a spelling mistake
                                errors.append('<br>')

                            # tell the user which form field has the spelling mistake
                            errors.append("<i>%s</i> contains spelling error(s):"%nice_name)

                            # add each spelling mistake to the error message
                            for word, suggest in new_errors.items():
                                errors.append('''<br>\
        &nbsp;&nbsp;<b style="color:yellow">%s</b> - <select><option>%s</option></select>'''% \
        (word, '</option><option>'.join(suggest)))

                self.form.value.remove(self.form[_aspell])

            return errors

    class Aspell_NewItemAction(AspellClass, NewItemAction):
        def handle(self):
            # perform spell check
            errors = self.check()

            if errors:
                # we have some spelling mistakes
                self.client.error_message.extend(errors)
                return

            else:
                # let RoundUp do the rest
                NewItemAction.handle(self)

    class Aspell_EditItemAction(AspellClass, actions.EditItemAction):
        def handle(self):
            # perform spell check
            errors = self.check()

            if errors:
                # we have some spelling mistakes
                self.client.error_message.extend(errors)
                return

            else:
                # let RoundUp do the rest
                actions.EditItemAction.handle(self)

    def init(instance):
        instance.registerAction('aspell_new_item', Aspell_NewItemAction)
        instance.registerAction('aspell_edit_item', Aspell_EditItemAction)

Here are the changes for the issue.item.html template.
Lookup this part:

     <td colspan=3>
      <span tal:replace="structure context/submit">submit button</span>
      <a tal:condition="context/id" tal:attributes="href context/copy_url"
       i18n:translate="">Make a copy</a>
     </td>

and replace it with:

     <td colspan=3>
      <tal:block tal:condition="not:context/id">
       <input type="hidden" name="@action" value="aspell_new">
       <input type="submit" name="submit" value="Submit New Entry">
      </tal:block>
      <tal:block tal:condition="context/id">
       <input type="hidden" name="@lastactivity" tal:attributes="value context/activity">
       <input type="hidden" name="@action" value="aspell_edit">
       <input type="submit" name="submit" value="Submit Changes">
       <a tal:attributes="href context/copy_url" i18n:translate="">Make a copy</a>
      </tal:block>
      <input type="hidden" name="@aspell" value="title:title,@note:message">
      <input type="checkbox" name="no_spell_check" value="1">No spell checking
     </td>