source: trunk/lib/SpellCheck.inc.php @ 176

Last change on this file since 176 was 176, checked in by scdev, 18 years ago

Q - added some sc-small classes to form inputs.

File size: 13.9 KB
Line 
1<?php
2/**
3 * SpellCheck.inc.php
4 * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
5 *
6 * Interface to PHP's pspell functions.
7 *
8 * @author  Quinn Comendant <quinn@strangecode.com>
9 * @version 1.1
10 */
11
12/* Implementation example:
13--------------------------------------------------------------------------------
14include_once dirname(__FILE__) . '/_config.inc.php';
15include 'codebase/lib/SpellCheck.inc.php';
16
17// Instantiate with parameters. In this example we'll set the language and the path to the personal wordlist file.
18$spell = new SpellCheck(array(
19    'language' => 'en',
20    'personal_wordlist' => '/tmp/my_custom_dict'
21));
22
23// Just for the heck of it add a new word to persistent personal wordlist file.
24$spell->add('mealworm');
25
26$text_to_check = 'donky rinds taste like mealworm paste';
27
28if (!$spell->checkString($text_to_check)) {
29    $suggestions = $spell->getStringSuggestions($text_to_check);
30    echo 'Spelling errors! Here are suggested alternatives:';
31    print_r($suggestions);
32} else {
33    echo 'No spelling errors';
34}
35
36// Save added words to persistent custom wordlist file.
37$spell->save();
38--------------------------------------------------------------------------------
39*/
40
41class SpellCheck {
42
43    var $_params = array(
44        'language' => 'en',
45        'personal_wordlist' => '', // Text file to save custom words to.
46        'skip_length' => 3, // Words with this many chars or less will not be checked.
47        'mode' => PSPELL_NORMAL, // PSPELL_FAST, PSPELL_NORMAL, or PSPELL_BAD_SPELLERS.
48        'highlight_start' => '<strong style="color:red;">',
49        'highlight_end' => '</strong>',
50    );
51
52    var $_pspell_cfg_handle;
53    var $_pspell_handle;
54    var $_use_personal_wordlist = false;
55    var $_errors = array();
56
57    /**
58     * Constructor.
59     *
60     * @param  array    $params     Array of parameters (key => val pairs).
61     */
62    function SpellCheck($params)
63    {
64        $app =& App::getInstance();
65
66        if (!is_array($params) || empty($params)) {
67            trigger_error('SpellCheck parameters not set properly', E_USER_ERROR);
68        }
69
70        $this->setParam($params);
71
72        $this->_pspell_cfg_handle = pspell_config_create($this->getParam('language'));
73
74        pspell_config_ignore($this->_pspell_cfg_handle, $this->getParam('skip_length'));
75        pspell_config_mode($this->_pspell_cfg_handle, $this->getParam('mode'));
76
77        if ('' != $this->getParam('personal_wordlist')) {
78            if (!is_writable(dirname($this->getParam('personal_wordlist'))) || !is_writable($this->getParam('personal_wordlist'))) {
79                $app->logMsg(sprintf('Personal wordlist file not writable: %s', $this->getParam('personal_wordlist')), LOG_WARNING, __FILE__, __LINE__);
80            } else {
81                pspell_config_personal($this->_pspell_cfg_handle, $this->getParam('personal_wordlist'));
82                $this->_use_personal_wordlist = true;
83                $app->logMsg(sprintf('Using personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
84            }
85        }
86
87        $this->_pspell_handle = pspell_new_config($this->_pspell_cfg_handle);
88    }
89
90    /**
91     * Set (or overwrite existing) parameters by passing an array of new parameters.
92     *
93     * @access public
94     * @param  array    $params     Array of parameters (key => val pairs).
95     */
96    function setParam($params)
97    {
98        $app =& App::getInstance();
99
100        if (isset($params) && is_array($params)) {
101            // Merge new parameters with old overriding only those passed.
102            $this->_params = array_merge($this->_params, $params);
103        } else {
104            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
105        }
106    }
107
108    /**
109     * Return the value of a parameter, if it exists.
110     *
111     * @access public
112     * @param string $param        Which parameter to return.
113     * @return mixed               Configured parameter value.
114     */
115    function getParam($param)
116    {
117        $app =& App::getInstance();
118   
119        if (isset($this->_params[$param])) {
120            return $this->_params[$param];
121        } else {
122            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
123            return null;
124        }
125    }
126
127    /**
128     * Check whether any errors have been triggered.
129     *
130     * @return bool   True if any errors were found, false otherwise.
131     */
132    function anyErrors()
133    {
134        return (sizeof($this->_errors) > 0);
135    }
136
137    /**
138     * Reset the error list.
139     */
140    function resetErrorList()
141    {
142        $this->_errors = array();
143    }
144
145    /**
146     * Check one word.
147     *
148     * @access  public
149     * @param   string  $word
150     * @return  bool    True if word is correct.
151     * @author  Quinn Comendant <quinn@strangecode.com>
152     * @version 1.0
153     * @since   09 Jun 2005 18:23:51
154     */
155    function check($word)
156    {
157        if (pspell_check($this->_pspell_handle, $word)) {
158            return true;
159        } else {
160            $this->_errors[] = $word;
161            return false;
162        }
163    }
164
165    /**
166     * Suggest the correct spelling for one misspelled word.
167     *
168     * @access  public
169     * @param   string  $word
170     * @return  array   Word suggestions.
171     * @author  Quinn Comendant <quinn@strangecode.com>
172     * @version 1.0
173     * @since   09 Jun 2005 18:23:51
174     */
175    function suggest($word)
176    {
177        return pspell_suggest($this->_pspell_handle, $word);
178    }
179
180    /**
181     * Add a word to a personal list.
182     *
183     * @access  public
184     * @param   string  $word
185     * @return  array   Word suggestions.
186     * @author  Quinn Comendant <quinn@strangecode.com>
187     * @version 1.0
188     * @since   09 Jun 2005 18:23:51
189     */
190    function add($word)
191    {
192        $app =& App::getInstance();
193
194        if ($this->_use_personal_wordlist) {
195            if (pspell_add_to_personal($this->_pspell_handle, $word)) {
196                $app->logMsg(sprintf('Added "%s" to personal wordlist: %s', $word, $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
197                return true;
198            } else {
199                $app->logMsg(sprintf('Failed adding "%s" to personal wordlist: %s', $word, $this->getParam('personal_wordlist')), LOG_WARNING, __FILE__, __LINE__);
200                return false;
201            }
202        }
203    }
204
205    /**
206     * Save personal list to file.
207     *
208     * @access  public
209     * @param   string  $word
210     * @return  array   Word suggestions.
211     * @author  Quinn Comendant <quinn@strangecode.com>
212     * @version 1.0
213     * @since   09 Jun 2005 18:23:51
214     */
215    function save()
216    {
217        $app =& App::getInstance();
218
219        if ($this->_use_personal_wordlist) {
220            if (pspell_save_wordlist($this->_pspell_handle)) {
221                $app->logMsg(sprintf('Saved personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
222                return true;
223            } else {
224                $app->logMsg(sprintf('Failed saving personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_ERR, __FILE__, __LINE__);
225                return false;
226            }
227        }
228    }
229
230    /**
231     * Returns an array of suggested words for each mispelled word in the given text.
232     * The first word of the returned array is the (possibly) misspelled word.
233     *
234     * @access  public
235     * @param   string  $string String to get suggestions for.
236     * @return  mixed   Array of suggested words or false if none.
237     * @author  Quinn Comendant <quinn@strangecode.com>
238     * @version 1.0
239     * @since   09 Jun 2005 21:29:49
240     */
241    function getStringSuggestions($string)
242    {
243        $corrections = array();
244        $words = preg_split('/([\W]+?)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
245        // Remove non-word elements.
246        $words = preg_grep('/\w+/', $words);
247
248        if (is_array($words) && !empty($words)) {
249            foreach ($words as $i => $word) {
250                if (!$this->check($word)) {
251                    $corrections[$i] = $this->suggest($word);
252                    // Keep the original spelling as one of the suggestions.
253                    array_unshift($corrections[$i], $word);
254                    array_unique($corrections[$i]);
255                }
256            }
257        }
258        if (is_array($corrections) && !empty($corrections)) {
259            return $corrections;
260        } else {
261            return false;
262        }
263    }
264
265    /**
266     * Checks all words in a given string.
267     *
268     * @access  public
269     * @param   string  $string     String to check.
270     * @return  void
271     * @author  Quinn Comendant <quinn@strangecode.com>
272     * @version 1.0
273     * @since   09 Jun 2005 22:11:27
274     */
275    function checkString($string)
276    {
277        $errors = array();
278        $words = preg_split('/([\W]+?)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
279        // Remove non-word elements.
280        $check_words = preg_grep('/\w+/', $words);
281
282        if (is_array($check_words) && !empty($check_words)) {
283            foreach ($check_words as $i => $word) {
284                if (!$this->check($word)) {
285                    $errors[] = $word;
286                }
287            }
288        }
289        if (empty($errors)) {
290            return true;
291        } else {
292            $this->_errors = $errors + $this->_errors;
293            return false;
294        }
295    }
296
297    /**
298     * Returns a given string with misspelled words highlighted.
299     *
300     * @access  public
301     * @param   string  $string     Text to highlight.
302     * @return  string  Highlighted text.
303     * @author  Quinn Comendant <quinn@strangecode.com>
304     * @version 1.0
305     * @since   09 Jun 2005 21:29:49
306     */
307    function getStringHighlighted($string, $show_footnote=false)
308    {
309        $words = preg_split('/([\W]+?)/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
310        $check_words = preg_grep('/\w+/', $words);
311        $cnt = 0;
312        if (is_array($check_words) && !empty($check_words)) {
313            foreach ($check_words as $i => $word) {
314                if (!$this->check($word)) {
315                    $footnote = $show_footnote ? '<sup>' . ++$cnt . '</sup>' : '';
316                    $words[$i] = $this->getParam('highlight_start') . $word . $this->getParam('highlight_end') . $footnote;
317                }
318            }
319        }
320        return join('', $words);
321    }
322
323    /**
324     * Prints the HTML for correcting all mispellings found in the text of one $_FORM element.
325     *
326     * @access  public
327     * @param   string  $form_name  Name of the form to check.
328     * @return  void
329     * @author  Quinn Comendant <quinn@strangecode.com>
330     * @version 1.0
331     * @since   09 Jun 2005 21:29:49
332     */
333    function printCorrectionForm($form_name)
334    {
335        ?>
336        <input name="<?php echo $form_name ?>" type="hidden" value="<?php echo getFormData($form_name) ?>" />
337        <?php
338
339        $form_words = $this->getStringSuggestions(getFormData($form_name));
340        if (is_array($form_words) && !empty($form_words)) {
341            ?><ol><?php
342            foreach ($form_words as $i => $words) {
343                ?>
344                <li>
345                <select name="spelling_suggestions[<?php echo $form_name ?>][<?php echo $i ?>]" onChange="document.forms[0].elements['spelling_corrections[<?php echo $form_name ?>][<?php echo $i ?>]'].value = this.value;">
346                <?php $original_word = array_shift($words); ?>
347                <option value="<?php echo $original_word ?>">(<?php echo $original_word ?>)</option>
348                <?php
349
350                foreach ($words as $suggestion) {
351                    ?>
352                    <option value="<?php echo $suggestion ?>"><?php echo $suggestion ?></option>
353                    <?php
354                }
355
356                ?>
357                </select>
358                <input type="text" name="spelling_corrections[<?php echo $form_name ?>][<?php echo $i ?>]" value="<?php echo $original_word ?>" class="sc-small" />
359                <?php if ($this->_use_personal_wordlist) { ?>
360                <input name="save_to_personal_wordlist[]" type="checkbox" value="<?php echo $i ?>" /><?php echo _("Learn spelling") ?>
361                <?php } ?>
362                </li>
363                <?php
364            }
365            ?></ol><?php
366        }
367    }
368
369    /**
370     * Tests if any form spelling corrections have been submitted.
371     *
372     * @access  public
373     * @return  bool    True if form spelling has been checked.
374     * @author  Quinn Comendant <quinn@strangecode.com>
375     * @version 1.0
376     * @since   09 Jun 2005 23:15:35
377     */
378    function anyFormCorrections()
379    {
380        return (false !== getFormData('spelling_suggestions', false)) || (false !== getFormData('spelling_corrections', false));
381    }
382
383    /**
384     * Replace the misspelled words in the text of a specified form with the corrections.
385     *
386     * @access  public
387     * @param   string  $form_name      Name of form to apply corrections to.
388     * @return  string  Corrected form text.
389     * @author  Quinn Comendant <quinn@strangecode.com>
390     * @version 1.0
391     * @since   09 Jun 2005 23:18:51
392     */
393    function applyFormCorrections($form_name)
394    {
395        $form_words = preg_split('/([\W]+?)/', getFormData($form_name), -1, PREG_SPLIT_DELIM_CAPTURE);
396        $suggestions = getFormData('spelling_suggestions');
397        $corrections = getFormData('spelling_corrections');
398
399        $form_words = array_diff($corrections[$form_name], array('')) + $suggestions[$form_name] + $form_words;
400        ksort($form_words);
401
402        if ($this->_use_personal_wordlist) {
403            $save_to_personal_wordlist = getFormData('save_to_personal_wordlist');
404            if (is_array($save_to_personal_wordlist) && !empty($save_to_personal_wordlist)) {
405                foreach ($save_to_personal_wordlist as $cust) {
406                    $this->add($form_words[$cust]);
407                }
408            }
409            $this->save();
410        }
411
412        if (is_array($form_words) && !empty($form_words)) {
413            return join('', $form_words);
414        } else {
415            return getFormData($form_name);
416        }
417    }
418
419} // End.
420
421?>
Note: See TracBrowser for help on using the repository browser.