source: branches/eli_branch/lib/SpellCheck.inc.php @ 530

Last change on this file since 530 was 439, checked in by anonymous, 11 years ago

added public and private keywords to all properties and methods, changed old classname constructor function to construct, removed more ?> closing tags

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