source: branches/1.1dev/lib/SpellCheck.inc.php @ 429

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