1<?php 2/** 3 * Copyright 2005-2016 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING for license information (LGPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 7 * 8 * @category Horde 9 * @copyright 2005-2016 Horde LLC 10 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 11 * @package SpellChecker 12 */ 13 14/** 15 * A spellcheck driver for the aspell/ispell binary. 16 * 17 * @author Chuck Hagenbuch <chuck@horde.org> 18 * @author Michael Slusarz <slusarz@horde.org> 19 * @category Horde 20 * @copyright 2005-2016 Horde LLC 21 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 22 * @package SpellChecker 23 */ 24class Horde_SpellChecker_Aspell extends Horde_SpellChecker 25{ 26 /** 27 * @param array $args Additional arguments: 28 * - path: (string) Path to the aspell binary. 29 */ 30 public function __construct(array $args = array()) 31 { 32 parent::__construct(array_merge(array( 33 'path' => 'aspell' 34 ), $args)); 35 } 36 37 /** 38 */ 39 public function spellCheck($text) 40 { 41 $ret = array( 42 'bad' => array(), 43 'suggestions' => array() 44 ); 45 46 if ($this->_params['html']) { 47 $input = strtr($text, "\n", ' '); 48 } else { 49 $words = $this->_getWords($text); 50 if (!count($words)) { 51 return $ret; 52 } 53 $input = implode(' ', $words); 54 } 55 56 // Descriptor array. 57 $descspec = array( 58 0 => array('pipe', 'r'), 59 1 => array('pipe', 'w'), 60 2 => array('pipe', 'w') 61 ); 62 63 $process = proc_open($this->_cmd(), $descspec, $pipes); 64 if (!is_resource($process)) { 65 throw new Horde_SpellChecker_Exception('Spellcheck failed. Command line: ' . $this->_cmd()); 66 } 67 68 // The '^' character tells aspell to spell check the entire line. 69 fwrite($pipes[0], '^' . $input); 70 fclose($pipes[0]); 71 72 // Read stdout. 73 $out = ''; 74 while (!feof($pipes[1])) { 75 $out .= fread($pipes[1], 8192); 76 } 77 fclose($pipes[1]); 78 79 // Read stderr. 80 $err = ''; 81 while (!feof($pipes[2])) { 82 $err .= fread($pipes[2], 8192); 83 } 84 fclose($pipes[2]); 85 86 // We can't rely on the return value of proc_close: 87 // http://bugs.php.net/bug.php?id=29123 88 proc_close($process); 89 90 if (strlen($out) === 0) { 91 throw new Horde_SpellChecker_Exception('Spellcheck failed. Command line: ' . $this->_cmd()); 92 } 93 94 // Parse output. 95 foreach (array_map('trim', explode("\n", $out)) as $line) { 96 if (!strlen($line)) { 97 continue; 98 } 99 100 @list(,$word,) = explode(' ', $line, 3); 101 102 if ($this->_inLocalDictionary($word) || 103 in_array($word, $ret['bad'])) { 104 continue; 105 } 106 107 switch ($line[0]) { 108 case '#': 109 // Misspelling with no suggestions. 110 $ret['bad'][] = $word; 111 $ret['suggestions'][] = array(); 112 break; 113 114 case '&': 115 // Suggestions. 116 $ret['bad'][] = $word; 117 $ret['suggestions'][] = array_slice(explode(', ', substr($line, strpos($line, ':') + 2)), 0, $this->_params['maxSuggestions']); 118 break; 119 } 120 } 121 122 return $ret; 123 } 124 125 /** 126 * Create the command line string. 127 * 128 * @return string The command to run. 129 */ 130 protected function _cmd() 131 { 132 $args = array('-a', '--encoding=UTF-8'); 133 134 switch ($this->_params['suggestMode']) { 135 case self::SUGGEST_FAST: 136 $args[] = '--sug-mode=fast'; 137 break; 138 139 case self::SUGGEST_SLOW: 140 $args[] = '--sug-mode=bad-spellers'; 141 break; 142 143 default: 144 $args[] = '--sug-mode=normal'; 145 } 146 147 $args[] = '--lang=' . escapeshellarg($this->_params['locale']); 148 $args[] = '--ignore=' . escapeshellarg(max($this->_params['minLength'] - 1, 0)); 149 150 if ($this->_params['html']) { 151 $args[] = '-H'; 152 $args[] = '--rem-html-check=alt'; 153 } 154 155 return escapeshellcmd($this->_params['path']) . ' ' . 156 implode(' ', $args); 157 } 158 159} 160