1<?php 2/** 3 * Aspell interface 4 * 5 * This library gives full access to aspell's pipe interface. Optionally it 6 * provides some of the functions from the pspell PHP extension by wrapping 7 * them to calls to the aspell binary. 8 * 9 * It can be simply dropped into code written for the pspell extension like 10 * the following 11 * 12 * if(!function_exists('pspell_suggest')){ 13 * define('PSPELL_COMP',1); 14 * require_once ("pspell_comp.php"); 15 * } 16 * 17 * Define the path to the aspell binary like this if needed: 18 * 19 * define('ASPELL_BIN','/path/to/aspell'); 20 * 21 * @author Andreas Gohr <andi@splitbrain.org> 22 * @todo Not all pspell functions are supported 23 * 24 */ 25 26// path to your aspell binary 27if(!defined('ASPELL_BIN')) define('ASPELL_BIN','aspell'); 28 29 30// different spelling modes supported by aspell 31if(!defined('PSPELL_FAST')) define(PSPELL_FAST,1); # Fast mode (least number of suggestions) 32if(!defined('PSPELL_NORMAL')) define(PSPELL_NORMAL,2); # Normal mode (more suggestions) 33if(!defined('PSPELL_BAD_SPELLERS')) define(PSPELL_BAD_SPELLERS,3); # Slow mode (a lot of suggestions) 34if(!defined('ASPELL_ULTRA')) define(ASPELL_ULTRA,4); # Ultra fast mode (not available in Pspell!) 35 36 37 38/** 39 * You can define PSPELL_COMP to use this class as drop in replacement 40 * for the pspell extension 41 */ 42if(defined('PSPELL_COMP')){ 43 // spelling is not supported by aspell and ignored 44 function pspell_config_create($language, $spelling=null, $jargon=null, $encoding='iso8859-1'){ 45 return new Aspell($language, $jargon, $encoding); 46 } 47 48 function pspell_config_mode(&$config, $mode){ 49 return $config->setMode($mode); 50 } 51 52 function pspell_new_config(&$config){ 53 return $config; 54 } 55 56 function pspell_check(&$dict,$word){ 57 return $dict->check($word); 58 } 59 60 function pspell_suggest(&$dict, $word){ 61 return $dict->suggest($word); 62 } 63} 64 65/** 66 * Class to interface aspell 67 * 68 * Needs PHP >= 4.3.0 69 */ 70class Aspell{ 71 var $language = null; 72 var $jargon = null; 73 var $personal = null; 74 var $encoding = 'iso8859-1'; 75 var $mode = PSPELL_NORMAL; 76 var $version = 0; 77 78 var $args=''; 79 80 /** 81 * Constructor. Works like pspell_config_create() 82 * 83 * @author Andreas Gohr <andi@splitbrain.org> 84 */ 85 function Aspell($language, $jargon=null, $encoding='iso8859-1'){ 86 $this->language = $language; 87 $this->jargon = $jargon; 88 $this->encoding = $encoding; 89 } 90 91 /** 92 * Set the spelling mode like pspell_config_mode() 93 * 94 * Mode can be PSPELL_FAST, PSPELL_NORMAL, PSPELL_BAD_SPELLER or ASPELL_ULTRA 95 * 96 * @author Andreas Gohr <andi@splitbrain.org> 97 */ 98 function setMode($mode){ 99 if(!in_array($mode,array(PSPELL_FAST,PSPELL_NORMAL,PSPELL_BAD_SPELLER,ASPELL_ULTRA))){ 100 $mode = PSPELL_NORMAL; 101 } 102 103 $this->mode = $mode; 104 return $mode; 105 } 106 107 /** 108 * Prepares the needed arguments for the call to the aspell binary 109 * 110 * No need to call this directly 111 * 112 * @author Andreas Gohr <andi@splitbrain.org> 113 */ 114 function _prepareArgs(){ 115 $this->args = ''; 116 117 if($this->language != null){ 118 $this->args .= ' --lang='.escapeshellarg($this->language); 119 }else{ 120 return false; // no lang no spell 121 } 122 123 if($this->jargon != null){ 124 $this->args .= ' --jargon='.escapeshellarg($this->jargon); 125 } 126 127 if($this->personal != null){ 128 $this->args .= ' --personal='.escapeshellarg($this->personal); 129 } 130 131 if($this->encoding != null){ 132 $this->args .= ' --encoding='.escapeshellarg($this->encoding); 133 } 134 135 switch ($this->mode){ 136 case PSPELL_FAST: 137 $this->args .= ' --sug-mode=fast'; 138 break; 139 case PSPELL_BAD_SPELLERS: 140 $this->args .= ' --sug-mode=bad-spellers'; 141 break; 142 case ASPELL_ULTRA: 143 $this->args .= ' --sug-mode=ultra'; 144 break; 145 default: 146 $this->args .= ' --sug-mode=normal'; 147 } 148 149 return true; 150 } 151 152 153 /** 154 * Pipes a text to aspell 155 * 156 * This opens a bidirectional pipe to the aspell binary, writes 157 * the given text to STDIN and returns STDOUT and STDERR 158 * 159 * You can give an array of special commands to be executed first 160 * as $specials parameter. Data lines are escaped automatically 161 * 162 * @author Andreas Gohr <andi@splitbrain.org> 163 * @link http://aspell.sf.net/man-html/Through-A-Pipe.html 164 */ 165 function runAspell($text,&$out,&$err,$specials=null){ 166 if(empty($text)) return true; 167 $terse = true; 168 169 // prepare arguments 170 $this->_prepareArgs(); 171 $command = ASPELL_BIN.' -a'.$this->args; 172 $stdin = ''; 173 174 // prepare specials 175 if(is_array($specials)){ 176 foreach($specials as $s){ 177 if ($s == '!') $terse = false; 178 $stdin .= "$s\n"; 179 } 180 } 181 182 // prepare text 183 $stdin .= "^".str_replace("\n", "\n^",$text); 184 185 // run aspell through the pipe 186 $rc = $this->execPipe($command,$stdin,$out,$err); 187 if(is_null($rc)){ 188 $err = "Could not run Aspell '".ASPELL_BIN."'"; 189 return false; 190 } 191 192 // Aspell has a bug that can't be autodetected because both versions 193 // might produce the same output but under different conditions. So 194 // we check Aspells version number here to divide broken and working 195 // versions of Aspell. 196 $tmp = array(); 197 preg_match('/^\@.*Aspell (\d+)\.(\d+).(\d+)/',$out,$tmp); 198 $this->version = $tmp[1]*100 + $tmp[2]*10 + $tmp[3]; 199 200 if ($this->version <= 603) // version 0.60.3 201 $r = $terse ? "\n*\n\$1" : "\n\$1"; // replacement for broken Aspell 202 else 203 $r = $terse ? "\n*\n" : "\n"; // replacement for good Aspell 204 205 // lines starting with a '?' are no realy misspelled words and some 206 // Aspell versions doesn't produce usable output anyway so we filter 207 // them out here. 208 $out = preg_replace('/\n\? [^\n\&\*]*([\n]?)/',$r, $out); 209 210 if ($err){ 211 //something went wrong 212 $err = "Aspell returned an error(".ASPELL_BIN." exitcode: $rc ):\n".$err; 213 return false; 214 } 215 return true; 216 } 217 218 219 /** 220 * Runs the given command with the given input on STDIN 221 * 222 * STDOUT and STDERR are written to the given vars, the command's 223 * exit code is returned. If the pip couldn't be opened null is returned 224 * 225 * @author <richard at 2006 dot atterer dot net> 226 * @link http://www.php.net/manual/en/function.proc-open.php#64116 227 */ 228 function execPipe($command,$stdin,&$stdout,&$stderr){ 229 $descriptorSpec = array(0 => array("pipe", "r"), 230 1 => array('pipe', 'w'), 231 2 => array('pipe', 'w')); 232 $process = proc_open($command, $descriptorSpec, $pipes); 233 if(!$process) return null; 234 235 $txOff = 0; 236 $txLen = strlen($stdin); 237 $stdoutDone = false; 238 $stderrDone = false; 239 240 stream_set_blocking($pipes[0], 0); // Make stdin/stdout/stderr non-blocking 241 stream_set_blocking($pipes[1], 0); 242 stream_set_blocking($pipes[2], 0); 243 244 if ($txLen == 0) fclose($pipes[0]); 245 while (true) { 246 $rx = array(); // The program's stdout/stderr 247 if (!$stdoutDone) $rx[] = $pipes[1]; 248 if (!$stderrDone) $rx[] = $pipes[2]; 249 $tx = array(); // The program's stdin 250 if ($txOff < $txLen) $tx[] = $pipes[0]; 251 stream_select($rx, $tx, $ex = NULL, NULL, NULL); // Block til r/w possible 252 253 if (!empty($tx)) { 254 $txRet = fwrite($pipes[0], substr($stdin, $txOff, 8192)); 255 if ($txRet !== false) $txOff += $txRet; 256 if ($txOff >= $txLen) fclose($pipes[0]); 257 } 258 259 foreach ($rx as $r) { 260 if ($r == $pipes[1]) { 261 $stdout .= fread($pipes[1], 8192); 262 if (feof($pipes[1])) { 263 fclose($pipes[1]); 264 $stdoutDone = true; 265 } 266 } else if ($r == $pipes[2]) { 267 $stderr .= fread($pipes[2], 8192); 268 if (feof($pipes[2])) { 269 fclose($pipes[2]); 270 $stderrDone = true; 271 } 272 } 273 } 274 if (!is_resource($process)) break; 275 if ($txOff >= $txLen && $stdoutDone && $stderrDone) break; 276 } 277 return proc_close($process); 278 } 279 280 281 282 283 /** 284 * Checks a single word for correctness 285 * 286 * @returns array of suggestions or true on correct spelling 287 * @author Andreas Gohr <andi@splitbrain.org> 288 */ 289 function suggest($word){ 290 if($this->runAspell("^$word",$out,$err)){ 291 //parse output 292 $lines = split("\n",$out); 293 foreach ($lines as $line){ 294 $line = trim($line); 295 if(empty($line)) continue; // empty line 296 if($line[0] == '@') continue; // comment 297 if($line[0] == '*') return true; // no mistakes made 298 if($line[0] == '#') return array(); // mistake but no suggestions 299 if($line[0] == '&'){ 300 $line = preg_replace('/&.*?: /','',$line); 301 return split(', ',$line); 302 } 303 } 304 } 305 return array(); 306 } 307 308 /** 309 * Check if a word is mispelled like pspell_check 310 * 311 * @author Andreas Gohr <andi@splitbrain.org> 312 */ 313 function check($word){ 314 if(is_array($this->suggest($word))){ 315 return false; 316 }else{ 317 return true; 318 } 319 } 320} 321 322//Setup VIM: ex: et ts=4 enc=utf-8 : 323