TYPO3  7.6
SpellCheckingController.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Rtehtmlarea\Controller;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
20 
25 {
29  protected $csConvObj;
30 
31  // The extension key
35  public $extKey = 'rtehtmlarea';
36 
40  public $siteUrl;
41 
45  public $charset = 'utf-8';
46 
50  public $parserCharset = 'utf-8';
51 
55  public $defaultAspellEncoding = 'utf-8';
56 
61 
65  public $result;
66 
70  public $text;
71 
75  public $misspelled = array();
76 
81 
85  public $wordCount = 0;
86 
90  public $suggestionCount = 0;
91 
95  public $suggestedWordCount = 0;
96 
100  public $pspell_link;
101 
105  public $pspellMode = 'normal';
106 
110  public $dictionary;
111 
116 
121 
125  public $forceCommandMode = 0;
126 
130  public $filePrefix = 'rtehtmlarea_';
131 
132  // Pre-FAL backward compatibility
133  protected $uploadFolder = 'uploads/tx_rtehtmlarea/';
134 
135  // Path to main dictionary
137 
138  // Path to personal dictionary
140 
144  public $xmlCharacterData = '';
145 
146 
156  {
157  return $this->processRequest($request, $response);
158  }
159 
170  {
171  $this->csConvObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Charset\CharsetConverter::class);
172  // Setting start time
173  $time_start = microtime(true);
174  $this->pspell_is_available = in_array('pspell', get_loaded_extensions());
175  $this->AspellDirectory = trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['plugins']['SpellChecker']['AspellDirectory']) ?: '/usr/bin/aspell';
176  // Setting command mode if requested and available
177  $this->forceCommandMode = trim($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$this->extKey]['plugins']['SpellChecker']['forceCommandMode']) ?: 0;
178  if (!$this->pspell_is_available || $this->forceCommandMode) {
179  $AspellVersionString = explode('Aspell', shell_exec($this->AspellDirectory . ' -v'));
180  $AspellVersion = substr($AspellVersionString[1], 0, 4);
181  if (doubleval($AspellVersion) < doubleval('0.5') && (!$this->pspell_is_available || $this->forceCommandMode)) {
182  echo 'Configuration problem: Aspell version ' . $AspellVersion . ' too old. Spell checking cannot be performed in command mode.';
183  }
184  $this->defaultAspellEncoding = trim(shell_exec($this->AspellDirectory . ' config encoding'));
185  }
186  // Setting the list of dictionaries
187  $dictionaryList = shell_exec($this->AspellDirectory . ' dump dicts');
188  $dictionaryList = implode(',', GeneralUtility::trimExplode(LF, $dictionaryList, true));
189  $dictionaryArray = GeneralUtility::trimExplode(',', $dictionaryList, true);
190  $restrictToDictionaries = GeneralUtility::_POST('restrictToDictionaries');
191  if ($restrictToDictionaries) {
192  $dictionaryArray = array_intersect($dictionaryArray, GeneralUtility::trimExplode(',', $restrictToDictionaries, 1));
193  }
194  if (empty($dictionaryArray)) {
195  $dictionaryArray[] = 'en';
196  }
197  $this->dictionary = GeneralUtility::_POST('dictionary');
198  $defaultDictionary = $this->dictionary;
199  if (!$defaultDictionary || !in_array($defaultDictionary, $dictionaryArray)) {
200  $defaultDictionary = 'en';
201  }
202  uasort($dictionaryArray, 'strcoll');
203  $dictionaryList = implode(',', $dictionaryArray);
204  // Setting the dictionary
205  if (empty($this->dictionary) || !in_array($this->dictionary, $dictionaryArray)) {
206  $this->dictionary = 'en';
207  }
208  // Setting the pspell suggestion mode
209  $this->pspellMode = GeneralUtility::_POST('pspell_mode') ? GeneralUtility::_POST('pspell_mode') : $this->pspellMode;
210  // Now sanitize $this->pspellMode
211  $this->pspellMode = GeneralUtility::inList('ultra,fast,normal,bad-spellers', $this->pspellMode) ? $this->pspellMode : 'normal';
212  switch ($this->pspellMode) {
213  case 'ultra':
214 
215  case 'fast':
216  $pspellModeFlag = PSPELL_FAST;
217  break;
218  case 'bad-spellers':
219  $pspellModeFlag = PSPELL_BAD_SPELLERS;
220  break;
221  case 'normal':
222 
223  default:
224  $pspellModeFlag = PSPELL_NORMAL;
225  }
226  // Setting the charset
227  if (GeneralUtility::_POST('pspell_charset')) {
228  $this->charset = trim(GeneralUtility::_POST('pspell_charset'));
229  }
230  if (strtolower($this->charset) == 'iso-8859-1') {
231  $this->parserCharset = strtolower($this->charset);
232  }
233  // In some configurations, Aspell uses 'iso8859-1' instead of 'iso-8859-1'
234  $this->aspellEncoding = $this->parserCharset;
235  if ($this->parserCharset == 'iso-8859-1' && strstr($this->defaultAspellEncoding, '8859-1')) {
236  $this->aspellEncoding = $this->defaultAspellEncoding;
237  }
238  // However, we are going to work only in the parser charset
239  if ($this->pspell_is_available && !$this->forceCommandMode) {
240  $this->pspell_link = pspell_new($this->dictionary, '', '', $this->parserCharset, $pspellModeFlag);
241  }
242  // Setting the path to main dictionary
243  $this->setMainDictionaryPath();
244  // Setting the path to user personal dictionary, if any
245  $this->setPersonalDictionaryPath();
247  $cmd = GeneralUtility::_POST('cmd');
248  if ($cmd == 'learn') {
249  // Only availble for BE_USERS, die silently if someone has gotten here by accident
250  if (TYPO3_MODE !== 'BE' || !is_object($GLOBALS['BE_USER'])) {
251  die('');
252  }
253  // Updating the personal word list
254  $to_p_dict = GeneralUtility::_POST('to_p_dict');
255  $to_p_dict = $to_p_dict ? $to_p_dict : array();
256  $to_r_list = GeneralUtility::_POST('to_r_list');
257  $to_r_list = $to_r_list ? $to_r_list : array();
258  header('Content-Type: text/plain; charset=' . strtoupper($this->parserCharset));
259  header('Pragma: no-cache');
260  if ($to_p_dict || $to_r_list) {
261  $tmpFileName = GeneralUtility::tempnam($this->filePrefix);
262  $filehandle = fopen($tmpFileName, 'wb');
263  if ($filehandle) {
264  // Get the character set of the main dictionary
265  // We need to convert the input into the character set of the main dictionary
266  $mainDictionaryCharacterSet = $this->getMainDictionaryCharacterSet();
267  // Write the personal words addition commands to the temporary file
268  foreach ($to_p_dict as $personal_word) {
269  $cmd = '&' . $this->csConvObj->conv($personal_word, $this->parserCharset, $mainDictionaryCharacterSet) . LF;
270  fwrite($filehandle, $cmd, strlen($cmd));
271  }
272  // Write the replacent pairs addition commands to the temporary file
273  foreach ($to_r_list as $replace_pair) {
274  $cmd = '$$ra ' . $this->csConvObj->conv($replace_pair[0], $this->parserCharset, $mainDictionaryCharacterSet) . ' , ' . $this->csConvObj->conv($replace_pair[1], $this->parserCharset, $mainDictionaryCharacterSet) . LF;
275  fwrite($filehandle, $cmd, strlen($cmd));
276  }
277  $cmd = '#' . LF;
278  $result = fwrite($filehandle, $cmd, strlen($cmd));
279  if ($result === false) {
280  GeneralUtility::sysLog('SpellChecker tempfile write error: ' . $tmpFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
281  } else {
282  // Assemble the Aspell command
283  $aspellCommand = ((TYPO3_OS === 'WIN') ? 'type ' : 'cat ') . escapeshellarg($tmpFileName) . ' | '
284  . $this->AspellDirectory
285  . ' -a --mode=none'
286  . ($this->personalDictionaryPath ? ' --home-dir=' . escapeshellarg($this->personalDictionaryPath) : '')
287  . ' --lang=' . escapeshellarg($this->dictionary)
288  . ' --encoding=' . escapeshellarg($mainDictionaryCharacterSet)
289  . ' 2>&1';
290  $aspellResult = shell_exec($aspellCommand);
291  // Close and delete the temporary file
292  fclose($filehandle);
293  GeneralUtility::unlink_tempfile($tmpFileName);
294  }
295  } else {
296  GeneralUtility::sysLog('SpellChecker tempfile open error: ' . $tmpFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
297  }
298  }
299  flush();
300  die;
301  } else {
302  // Check spelling content
303  // Initialize output
304  $this->result = '<?xml version="1.0" encoding="' . $this->parserCharset . '"?>
305 <!DOCTYPE html
306  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
307  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
308 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . substr($this->dictionary, 0, 2) . '" lang="' . substr($this->dictionary, 0, 2) . '">
309 <head>
310 <meta http-equiv="Content-Type" content="text/html; charset=' . $this->parserCharset . '" />
311 <link rel="stylesheet" type="text/css" media="all" href="' . (TYPO3_MODE == 'BE' ? '../' : '') . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath($this->extKey) . '/Resources/Public/Css/Skin/Plugins/spell-checker-iframe.css" />
312 <script type="text/javascript">
313 /*<![CDATA[*/
314 <!--
315 ';
316  // Getting the input content
317  $content = GeneralUtility::_POST('content');
318  // Parsing the input HTML
319  $parser = xml_parser_create(strtoupper($this->parserCharset));
320  xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
321  xml_set_object($parser, $this);
322  if (!xml_set_element_handler($parser, 'startHandler', 'endHandler')) {
323  echo 'Bad xml handler setting';
324  }
325  if (!xml_set_character_data_handler($parser, 'collectDataHandler')) {
326  echo 'Bad xml handler setting';
327  }
328  if (!xml_set_default_handler($parser, 'defaultHandler')) {
329  echo 'Bad xml handler setting';
330  }
331  if (!xml_parse($parser, ('<?xml version="1.0" encoding="' . $this->parserCharset . '"?><spellchecker> ' . preg_replace(('/&nbsp;/' . ($this->parserCharset == 'utf-8' ? 'u' : '')), ' ', $content) . ' </spellchecker>'))) {
332  echo 'Bad parsing';
333  }
334  if (xml_get_error_code($parser)) {
335  throw new \UnexpectedValueException('Line ' . xml_get_current_line_number($parser) . ': ' . xml_error_string(xml_get_error_code($parser)), 1294585788);
336  }
337  xml_parser_free($parser);
338  if ($this->pspell_is_available && !$this->forceCommandMode) {
339  pspell_clear_session($this->pspell_link);
340  }
341  $this->result .= 'var suggestedWords = {' . $this->suggestedWords . '};
342 var dictionaries = "' . $dictionaryList . '";
343 var selectedDictionary = "' . $this->dictionary . '";
344 ';
345  // Calculating parsing and spell checkting time
346  $time = number_format(microtime(true) - $time_start, 2, ',', ' ');
347  // Insert spellcheck info
348  $this->result .= 'var spellcheckInfo = { "Total words":"' . $this->wordCount . '","Misspelled words":"' . sizeof($this->misspelled) . '","Total suggestions":"' . $this->suggestionCount . '","Total words suggested":"' . $this->suggestedWordCount . '","Spelling checked in":"' . $time . '" };
349 // -->
350 /*]]>*/
351 </script>
352 </head>
353 ';
354  $this->result .= '<body onload="window.parent.RTEarea[\'' . GeneralUtility::_POST('editorId') . '\'].editor.getPlugin(\'SpellChecker\').spellCheckComplete();">';
355  $this->result .= preg_replace('/' . preg_quote('<?xml') . '.*' . preg_quote('?>') . '[' . preg_quote((LF . CR . chr(32))) . ']*/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '', $this->text);
356  $this->result .= '<div style="display: none;">' . $dictionaries . '</div>';
357  // Closing
358  $this->result .= '
359 </body></html>';
360  // Outputting
361  $response = $response->withHeader('Content-Type', 'text/html; charset=' . strtoupper($this->parserCharset));
362  $response->getBody()->write($this->result);
363  return $response;
364  }
365  }
366 
372  protected function setMainDictionaryPath()
373  {
374  $this->mainDictionaryPath = '';
375  $aspellCommand = $this->AspellDirectory . ' config dict-dir';
376  $aspellResult = shell_exec($aspellCommand);
377  if ($aspellResult) {
378  $this->mainDictionaryPath = trim($aspellResult);
379  }
380  if (!$aspellResult || !$this->mainDictionaryPath) {
381  GeneralUtility::sysLog('SpellChecker main dictionary path retrieval error: ' . $aspellCommand, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
382  }
384  }
385 
391  protected function getMainDictionaryCharacterSet()
392  {
393  $characterSet = '';
394  if ($this->mainDictionaryPath) {
395  // Keep only the first part of the dictionary name
396  $mainDictionary = preg_split('/[-_]/', $this->dictionary, 2);
397  // Read the options of the dictionary
398  $dictionaryFileName = $this->mainDictionaryPath . '/' . $mainDictionary[0] . '.dat';
399  $dictionaryHandle = fopen($dictionaryFileName, 'rb');
400  if (!$dictionaryHandle) {
401  GeneralUtility::sysLog('SpellChecker main dictionary open error: ' . $dictionaryFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
402  } else {
403  $dictionaryContent = fread($dictionaryHandle, 500);
404  if ($dictionaryContent === false) {
405  GeneralUtility::sysLog('SpellChecker main dictionary read error: ' . $dictionaryFileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
406  } else {
407  fclose($dictionaryHandle);
408  // Get the line that contains the character set option
409  $dictionaryContent = preg_split('/charset\s*/', $dictionaryContent, 2);
410  if ($dictionaryContent[1]) {
411  // Isolate the character set
412  $dictionaryContent = GeneralUtility::trimExplode(LF, $dictionaryContent[1]);
413  // Fix Aspell character set oddity (i.e. iso8859-1)
414  $characterSet = str_replace(
415  array('iso', '--'),
416  array('iso-', '-'),
417  $dictionaryContent[0]
418  );
419  }
420  if (!$characterSet) {
421  GeneralUtility::sysLog('SpellChecker main dictionary character set retrieval error: ' . $dictionaryContent[1], $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
422  }
423  }
424  }
425  }
426  return $characterSet;
427  }
428 
434  protected function setPersonalDictionaryPath()
435  {
436  $this->personalDictionaryPath = '';
437  if (GeneralUtility::_POST('enablePersonalDicts') == 'true' && TYPO3_MODE == 'BE' && is_object($GLOBALS['BE_USER'])) {
438  if ($GLOBALS['BE_USER']->user['uid']) {
439  $personalDictionaryFolderName = 'BE_' . $GLOBALS['BE_USER']->user['uid'];
440  // Check for pre-FAL personal dictionary folder
441  try {
442  $personalDictionaryFolder = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier(PATH_site . $this->uploadFolder . $personalDictionaryFolderName);
443  } catch (\Exception $e) {
444  $personalDictionaryFolder = false;
445  }
446  // The personal dictionary folder is created in the user's default upload folder and named BE_(uid)_personaldictionary
447  if (!$personalDictionaryFolder) {
448  $personalDictionaryFolderName .= '_personaldictionary';
449  $backendUserDefaultFolder = $GLOBALS['BE_USER']->getDefaultUploadFolder();
450  if ($backendUserDefaultFolder->hasFolder($personalDictionaryFolderName)) {
451  $personalDictionaryFolder = $backendUserDefaultFolder->getSubfolder($personalDictionaryFolderName);
452  } else {
453  $personalDictionaryFolder = $backendUserDefaultFolder->createFolder($personalDictionaryFolderName);
454  }
455  }
456  $this->personalDictionaryPath = PATH_site . rtrim($personalDictionaryFolder->getPublicUrl(), '/');
457  }
458  }
460  }
461 
468  {
469  if ($this->personalDictionaryPath) {
470  // Fix the options of the personl word list and of the replacement pairs files
471  // Aspell creates such files only for the main dictionary
472  $fileNames = array();
473  $mainDictionary = preg_split('/[-_]/', $this->dictionary, 2);
474  $fileNames[0] = $this->personalDictionaryPath . '/' . '.aspell.' . $mainDictionary[0] . '.pws';
475  $fileNames[1] = $this->personalDictionaryPath . '/' . '.aspell.' . $mainDictionary[0] . '.prepl';
476  foreach ($fileNames as $fileName) {
477  if (file_exists($fileName)) {
478  $fileContent = file_get_contents($fileName);
479  if ($fileContent === false) {
480  GeneralUtility::sysLog('SpellChecker personal word list read error: ' . $fileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
481  } else {
482  $fileContent = explode(LF, $fileContent);
483  if (strpos($fileContent[0], 'utf-8') === false) {
484  $fileContent[0] .= ' utf-8';
485  $fileContent = implode(LF, $fileContent);
486  $result = file_put_contents($fileName, $fileContent);
487  if ($result === false) {
488  GeneralUtility::sysLog('SpellChecker personal word list write error: ' . $fileName, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_ERROR);
489  }
490  }
491  }
492  }
493  }
494  }
495  }
496 
500  public function startHandler($xml_parser, $tag, $attributes)
501  {
502  if ((string)$this->xmlCharacterData !== '') {
503  $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
504  $this->xmlCharacterData = '';
505  }
506  switch ($tag) {
507  case 'spellchecker':
508  break;
509  case 'br':
510 
511  case 'BR':
512 
513  case 'img':
514 
515  case 'IMG':
516 
517  case 'hr':
518 
519  case 'HR':
520 
521  case 'area':
522 
523  case 'AREA':
524  $this->text .= '<' . $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
525  foreach ($attributes as $key => $val) {
526  $this->text .= $key . '="' . $val . '" ';
527  }
528  $this->text .= ' />';
529  break;
530  default:
531  $this->text .= '<' . $this->csConvObj->conv_case($this->parserCharset, $tag, 'toLower') . ' ';
532  foreach ($attributes as $key => $val) {
533  $this->text .= $key . '="' . $val . '" ';
534  }
535  $this->text .= '>';
536  }
537  }
538 
542  public function endHandler($xml_parser, $tag)
543  {
544  if ((string)$this->xmlCharacterData !== '') {
545  $this->spellCheckHandler($xml_parser, $this->xmlCharacterData);
546  $this->xmlCharacterData = '';
547  }
548  switch ($tag) {
549  case 'spellchecker':
550 
551  case 'br':
552 
553  case 'BR':
554 
555  case 'img':
556 
557  case 'IMG':
558 
559  case 'hr':
560 
561  case 'HR':
562 
563  case 'input':
564 
565  case 'INPUT':
566 
567  case 'area':
568 
569  case 'AREA':
570  break;
571  default:
572  $this->text .= '</' . $tag . '>';
573  }
574  }
575 
579  public function spellCheckHandler($xml_parser, $string)
580  {
581  $incurrent = array();
582  $stringText = $string;
583  $words = preg_split($this->parserCharset == 'utf-8' ? '/\\P{L}+/u' : '/\\W+/', $stringText);
584  foreach ($words as $word) {
585  $word = preg_replace('/ /' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '', $word);
586  if ($word && !is_numeric($word)) {
587  if ($this->pspell_is_available && !$this->forceCommandMode) {
588  if (!pspell_check($this->pspell_link, $word)) {
589  if (!in_array($word, $this->misspelled)) {
590  if (sizeof($this->misspelled) != 0) {
591  $this->suggestedWords .= ',';
592  }
593  $suggest = array();
594  $suggest = pspell_suggest($this->pspell_link, $word);
595  if (sizeof($suggest) != 0) {
596  $this->suggestionCount++;
597  $this->suggestedWordCount += sizeof($suggest);
598  }
599  $this->suggestedWords .= '"' . $word . '":"' . implode(',', $suggest) . '"';
600  $this->misspelled[] = $word;
601  unset($suggest);
602  }
603  if (!in_array($word, $incurrent)) {
604  $stringText = preg_replace('/\\b' . $word . '\\b/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '<span class="htmlarea-spellcheck-error">' . $word . '</span>', $stringText);
605  $incurrent[] = $word;
606  }
607  }
608  } else {
609  $tmpFileName = GeneralUtility::tempnam($this->filePrefix);
610  if (!($filehandle = fopen($tmpFileName, 'wb'))) {
611  echo 'SpellChecker tempfile open error';
612  }
613  if (!fwrite($filehandle, $word)) {
614  echo 'SpellChecker tempfile write error';
615  }
616  if (!fclose($filehandle)) {
617  echo 'SpellChecker tempfile close error';
618  }
619  $catCommand = TYPO3_OS === 'WIN' ? 'type' : 'cat';
620  $AspellCommand = $catCommand . ' ' . escapeshellarg($tmpFileName) . ' | '
621  . $this->AspellDirectory
622  . ' -a check'
623  . ' --mode=none'
624  . ' --sug-mode=' . escapeshellarg($this->pspellMode)
625  . ($this->personalDictionaryPath ? ' --home-dir=' . escapeshellarg($this->personalDictionaryPath) : '')
626  . ' --lang=' . escapeshellarg($this->dictionary)
627  . ' --encoding=' . escapeshellarg($this->aspellEncoding)
628  . ' 2>&1';
629  $AspellAnswer = shell_exec($AspellCommand);
630  $AspellResultLines = array();
631  $AspellResultLines = GeneralUtility::trimExplode(LF, $AspellAnswer, true);
632  if (substr($AspellResultLines[0], 0, 6) == 'Error:') {
633  echo '{' . $AspellAnswer . '}';
634  }
635  GeneralUtility::unlink_tempfile($tmpFileName);
636  if ($AspellResultLines['1'][0] !== '*') {
637  if (!in_array($word, $this->misspelled)) {
638  if (sizeof($this->misspelled) != 0) {
639  $this->suggestedWords .= ',';
640  }
641  $suggest = array();
642  $suggestions = array();
643  if ($AspellResultLines['1'][0] === '&') {
644  $suggestions = GeneralUtility::trimExplode(':', $AspellResultLines['1'], true);
645  $suggest = GeneralUtility::trimExplode(',', $suggestions['1'], true);
646  }
647  if (sizeof($suggest) != 0) {
648  $this->suggestionCount++;
649  $this->suggestedWordCount += sizeof($suggest);
650  }
651  $this->suggestedWords .= '"' . $word . '":"' . implode(',', $suggest) . '"';
652  $this->misspelled[] = $word;
653  unset($suggest);
654  unset($suggestions);
655  }
656  if (!in_array($word, $incurrent)) {
657  $stringText = preg_replace('/\\b' . $word . '\\b/' . ($this->parserCharset == 'utf-8' ? 'u' : ''), '<span class="htmlarea-spellcheck-error">' . $word . '</span>', $stringText);
658  $incurrent[] = $word;
659  }
660  }
661  unset($AspellResultLines);
662  }
663  $this->wordCount++;
664  }
665  }
666  $this->text .= $stringText;
667  unset($incurrent);
668  }
669 
673  public function collectDataHandler($xml_parser, $string)
674  {
675  $this->xmlCharacterData .= $string;
676  }
677 
681  public function defaultHandler($xml_parser, $string)
682  {
683  $this->text .= $string;
684  }
685 }