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

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