* Copyright 2001-2012 Strangecode, LLC
*
* This file is part of The Strangecode Codebase.
*
* The Strangecode Codebase is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* The Strangecode Codebase is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* The Strangecode Codebase. If not, see .
*/
/**
* SpellCheck.inc.php
*
* Interface to PHP's pspell functions.
*
* @author Quinn Comendant
* @version 1.1
*/
/* Implementation example:
--------------------------------------------------------------------------------
include_once dirname(__FILE__) . '/_config.inc.php';
include 'codebase/lib/SpellCheck.inc.php';
// Instantiate with parameters. In this example we'll set the language and the path to the personal wordlist file.
$spell = new SpellCheck(array(
'language' => 'en',
'personal_wordlist' => '/tmp/my_custom_dict'
));
// Just for the heck of it add a new word to persistent personal wordlist file.
$spell->add('mealworm');
$text_to_check = 'donky rinds taste like mealworm paste';
if (!$spell->checkString($text_to_check)) {
$suggestions = $spell->getStringSuggestions($text_to_check);
echo 'Spelling errors! Here are suggested alternatives:';
print_r($suggestions);
} else {
echo 'No spelling errors';
}
// Save added words to persistent custom wordlist file.
$spell->save();
--------------------------------------------------------------------------------
*/
class SpellCheck
{
protected $_params = array(
'language' => 'en',
'personal_wordlist' => '', // Text file to save custom words to.
'skip_length' => 3, // Words with this many chars or less will not be checked.
'mode' => PSPELL_NORMAL, // PSPELL_FAST, PSPELL_NORMAL, or PSPELL_BAD_SPELLERS.
'highlight_start' => '',
'highlight_end' => '',
);
protected $_pspell_cfg_handle;
protected $_pspell_handle;
protected $_use_personal_wordlist = false;
protected $_errors = array();
/**
* Constructor.
*
* @param array $params Array of parameters (key => val pairs).
*/
public function __construct($params)
{
$app =& App::getInstance();
if (!extension_loaded('pspell')) {
trigger_error('Pspell module not installed', E_USER_ERROR);
}
if (!is_array($params) || empty($params)) {
trigger_error('SpellCheck parameters not set properly', E_USER_ERROR);
}
$this->setParam($params);
$this->_pspell_cfg_handle = pspell_config_create($this->getParam('language'));
pspell_config_ignore($this->_pspell_cfg_handle, $this->getParam('skip_length'));
pspell_config_mode($this->_pspell_cfg_handle, $this->getParam('mode'));
if ('' != $this->getParam('personal_wordlist')) {
if (!is_writable(dirname($this->getParam('personal_wordlist'))) || !is_writable($this->getParam('personal_wordlist'))) {
$app->logMsg(sprintf('Personal wordlist file not writable: %s', $this->getParam('personal_wordlist')), LOG_WARNING, __FILE__, __LINE__);
} else {
pspell_config_personal($this->_pspell_cfg_handle, $this->getParam('personal_wordlist'));
$this->_use_personal_wordlist = true;
}
}
$this->_pspell_handle = pspell_new_config($this->_pspell_cfg_handle);
}
/**
* Set (or overwrite existing) parameters by passing an array of new parameters.
*
* @access public
* @param array $params Array of parameters (key => val pairs).
*/
public function setParam($params)
{
$app =& App::getInstance();
if (isset($params) && is_array($params)) {
// Merge new parameters with old overriding only those passed.
$this->_params = array_merge($this->_params, $params);
} else {
$app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
}
}
/**
* Return the value of a parameter, if it exists.
*
* @access public
* @param string $param Which parameter to return.
* @return mixed Configured parameter value.
*/
public function getParam($param)
{
$app =& App::getInstance();
if (array_key_exists($param, $this->_params)) {
return $this->_params[$param];
} else {
$app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
return null;
}
}
/**
* Check whether any errors have been triggered.
*
* @return bool True if any errors were found, false otherwise.
*/
public function anyErrors()
{
return (sizeof($this->_errors) > 0);
}
/**
* Reset the error list.
*/
public function resetErrorList()
{
$this->_errors = array();
}
/**
* Check one word.
*
* @access public
* @param string $word
* @return bool True if word is correct.
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 18:23:51
*/
public function check($word)
{
if (pspell_check($this->_pspell_handle, $word)) {
return true;
} else {
$this->_errors[] = $word;
return false;
}
}
/**
* Suggest the correct spelling for one misspelled word.
*
* @access public
* @param string $word
* @return array Word suggestions.
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 18:23:51
*/
public function suggest($word)
{
return pspell_suggest($this->_pspell_handle, $word);
}
/**
* Add a word to a personal list.
*
* @access public
* @param string $word
* @return array Word suggestions.
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 18:23:51
*/
public function add($word)
{
$app =& App::getInstance();
if ($this->_use_personal_wordlist) {
if (pspell_add_to_personal($this->_pspell_handle, $word)) {
$app->logMsg(sprintf('Added "%s" to personal wordlist: %s', $word, $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
return true;
} else {
$app->logMsg(sprintf('Failed adding "%s" to personal wordlist: %s', $word, $this->getParam('personal_wordlist')), LOG_NOTICE, __FILE__, __LINE__);
return false;
}
}
}
/**
* Save personal list to file.
*
* @access public
* @param string $word
* @return array Word suggestions.
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 18:23:51
*/
public function save()
{
$app =& App::getInstance();
if ($this->_use_personal_wordlist) {
if (pspell_save_wordlist($this->_pspell_handle)) {
$app->logMsg(sprintf('Saved personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_DEBUG, __FILE__, __LINE__);
return true;
} else {
$app->logMsg(sprintf('Failed saving personal wordlist: %s', $this->getParam('personal_wordlist')), LOG_ERR, __FILE__, __LINE__);
return false;
}
}
}
/**
* Returns an array of suggested words for each misspelled word in the given text.
* The first word of the returned array is the (possibly) misspelled word.
*
* @access public
* @param string $string String to get suggestions for.
* @return mixed Array of suggested words or false if none.
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 21:29:49
*/
public function getStringSuggestions($string)
{
$corrections = array();
// Split words on punctuation except apostrophes (this regex is used in several places in this class).
// http://stackoverflow.com/questions/790596/split-a-text-into-single-words
$words = preg_split("/((?:^\p{P}+)|(?:\p{P}*\s+\p{P}*)|[\p{Pd}—–-]+|(?:\p{P}+$))/", $string, -1, PREG_SPLIT_DELIM_CAPTURE);
if (is_array($words) && !empty($words)) {
// Remove non-word elements.
$words = preg_grep('/\w+/', $words);
$words = array_map('strip_tags', $words);
foreach ($words as $i => $word) {
if (!$this->check($word)) {
$corrections[$i] = $this->suggest($word);
// Keep the original spelling as one of the suggestions.
array_unshift($corrections[$i], $word);
array_unique($corrections[$i]);
}
}
}
if (is_array($corrections) && !empty($corrections)) {
return $corrections;
} else {
return false;
}
}
/**
* Checks all words in a given string.
*
* @access public
* @param string $string String to check.
* @return void
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 22:11:27
*/
public function checkString($string)
{
$errors = array();
// Split words on punctuation except apostrophes (this regex is used in several places in this class).
// http://stackoverflow.com/questions/790596/split-a-text-into-single-words
$words = preg_split("/((?:^\p{P}+)|(?:\p{P}*\s+\p{P}*)|[\p{Pd}—–-]+|(?:\p{P}+$))/", $string, -1, PREG_SPLIT_DELIM_CAPTURE);
if (is_array($words) && !empty($words)) {
// Remove non-word elements.
$words = preg_grep('/\w+/', $words);
$words = array_map('strip_tags', $words);
foreach ($words as $i => $word) {
if (!$this->check($word)) {
$errors[] = $word;
}
}
}
if (empty($errors)) {
return true;
} else {
$this->_errors = $errors + $this->_errors;
return false;
}
}
/**
* Returns a given string with misspelled words highlighted.
*
* @access public
* @param string $string Text to highlight.
* @return string Highlighted text.
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 21:29:49
*/
public function getStringHighlighted($string, $show_footnote=false)
{
// Split words on punctuation except apostrophes (this regex is used in several places in this class).
// http://stackoverflow.com/questions/790596/split-a-text-into-single-words
$words = preg_split("/((?:^\p{P}+)|(?:\p{P}*\s+\p{P}*)|[\p{Pd}—–-]+|(?:\p{P}+$))/", $string, -1, PREG_SPLIT_DELIM_CAPTURE);
$cnt = 0;
if (is_array($words) && !empty($words)) {
$words = preg_grep('/\w+/', $words);
$words = array_map('strip_tags', $words);
foreach ($words as $i => $word) {
if (!$this->check($word)) {
$footnote = $show_footnote ? '' . ++$cnt . '' : '';
$words[$i] = $this->getParam('highlight_start') . $word . $this->getParam('highlight_end') . $footnote;
$string = preg_replace(sprintf('/\b%s\b/', preg_quote($word, '/')), $words[$i], $string);
}
}
}
return $string;
}
/**
* Prints the HTML for correcting all misspellings found in the text of one $_FORM element.
*
* @access public
* @param string $form_name Name of the form to check.
* @return void
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 21:29:49
*/
public function printCorrectionForm($form_name)
{
?>
getStringSuggestions(getFormData($form_name));
if (is_array($form_words) && !empty($form_words)) {
?> $words) {
?>
_use_personal_wordlist) { ?>
* @version 1.0
* @since 09 Jun 2005 23:15:35
*/
public function anyFormCorrections()
{
return (false !== getFormData('spelling_suggestions', false)) || (false !== getFormData('spelling_corrections', false));
}
/**
* Replace the misspelled words in the text of a specified form with the corrections.
*
* @access public
* @param string $form_name Name of form to apply corrections to.
* @return string Corrected form text.
* @author Quinn Comendant
* @version 1.0
* @since 09 Jun 2005 23:18:51
*/
public function applyFormCorrections($form_name)
{
// Split words on punctuation except apostrophes (this regex is used in several places in this class).
// http://stackoverflow.com/questions/790596/split-a-text-into-single-words
$form_words = preg_split("/((?:^\p{P}+)|(?:\p{P}*\s+\p{P}*)|[\p{Pd}—–-]+|(?:\p{P}+$))/", getFormData($form_name), -1, PREG_SPLIT_DELIM_CAPTURE);
$suggestions = getFormData('spelling_suggestions');
$corrections = getFormData('spelling_corrections');
$form_words = array_diff($corrections[$form_name], array('')) + $suggestions[$form_name] + $form_words;
ksort($form_words);
if ($this->_use_personal_wordlist) {
$save_to_personal_wordlist = getFormData('save_to_personal_wordlist');
if (is_array($save_to_personal_wordlist) && !empty($save_to_personal_wordlist)) {
foreach ($save_to_personal_wordlist as $cust) {
$this->add($form_words[$cust]);
}
}
$this->save();
}
if (is_array($form_words) && !empty($form_words)) {
return join('', $form_words);
} else {
return getFormData($form_name);
}
}
} // End.