00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #ifdef HAVE_CONFIG_H
00021 #include <config.h>
00022 #endif
00023
00024
00025 #include <qtextcodec.h>
00026 #include <qtimer.h>
00027 #include <kdebug.h>
00028 #include <klocale.h>
00029 #include <kprocio.h>
00030 #include "kospell.h"
00031 #include <qfileinfo.h>
00032 #include <qdir.h>
00033 #include <kglobal.h>
00034 #define MAXLINELENGTH 10000
00035
00036 class KoSpell::KoSpellPrivate
00037 {
00038 public:
00039 bool endOfResponse;
00040 bool m_bIgnoreUpperWords;
00041 bool m_bIgnoreTitleCase;
00042 };
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061 #define OUTPUT(x) (connect (proc, SIGNAL (readReady(KProcIO *)), this, SLOT (x(KProcIO *))))
00062
00063
00064 #define NOOUTPUT(x) (disconnect (proc, SIGNAL (readReady(KProcIO *)), this, SLOT (x(KProcIO *))))
00065
00066
00067
00068 KoSpell::KoSpell(QWidget *, QObject *obj, const char *slot, KSpellConfig *_ksc)
00069 {
00070 d=new KoSpellPrivate;
00071
00072 d->m_bIgnoreUpperWords=false;
00073 d->m_bIgnoreTitleCase=false;
00074
00075 proc=0;
00076 ksconfig=0;
00077
00078 if (_ksc!=0)
00079 ksconfig = new KSpellConfig(*_ksc);
00080 else
00081 ksconfig = new KSpellConfig;
00082 codec = 0;
00083 switch (ksconfig->encoding())
00084 {
00085 case KS_E_LATIN1:
00086 codec = QTextCodec::codecForName("ISO 8859-1");
00087 break;
00088 case KS_E_LATIN2:
00089 codec = QTextCodec::codecForName("ISO 8859-2");
00090 break;
00091 case KS_E_LATIN3:
00092 codec = QTextCodec::codecForName("ISO 8859-3");
00093 break;
00094 case KS_E_LATIN4:
00095 codec = QTextCodec::codecForName("ISO 8859-4");
00096 break;
00097 case KS_E_LATIN5:
00098 codec = QTextCodec::codecForName("ISO 8859-5");
00099 break;
00100 case KS_E_LATIN7:
00101 codec = QTextCodec::codecForName("ISO 8859-7");
00102 break;
00103 case KS_E_LATIN8:
00104 codec = QTextCodec::codecForName("ISO 8859-8");
00105 break;
00106 case KS_E_LATIN9:
00107 codec = QTextCodec::codecForName("ISO 8859-9");
00108 break;
00109 case KS_E_LATIN13:
00110 codec = QTextCodec::codecForName("ISO 8859-13");
00111 break;
00112 case KS_E_LATIN15:
00113 codec = QTextCodec::codecForName("ISO 8859-15");
00114 break;
00115 case KS_E_UTF8:
00116 codec = QTextCodec::codecForName("UTF-8");
00117 break;
00118 case KS_E_KOI8R:
00119 codec = QTextCodec::codecForName("KOI8-R");
00120 break;
00121 case KS_E_KOI8U:
00122 codec = QTextCodec::codecForName("KOI8-U");
00123 break;
00124 default:
00125 break;
00126 }
00127
00128 kdDebug(32500) << __FILE__ << ":" << __LINE__ << " Codec = " << (codec ? codec->name() : "<default>") << endl;
00129
00130 m_status = Starting;
00131
00132 ignorelist += ksconfig->ignoreList();
00133
00134
00135 if ( obj && slot )
00136 connect (this, SIGNAL(ready(KoSpell *)), obj, slot);
00137
00138 proc=new KProcIO(codec);
00139
00140 trystart=0;
00141 maxtrystart=2;
00142 startIspell();
00143 }
00144
00145
00146 void KoSpell::startIspell()
00147 {
00148 kdDebug(32500) << "Try #" << trystart << endl;
00149 if (trystart>0)
00150 proc->resetAll();
00151
00152 switch (ksconfig->client())
00153 {
00154 case KS_CLIENT_ISPELL:
00155 *proc << "ispell";
00156 kdDebug(32500) << "Using ispell" << endl;
00157 break;
00158 case KS_CLIENT_ASPELL:
00159 *proc << "aspell";
00160 kdDebug(32500) << "Using aspell" << endl;
00161 break;
00162 }
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172 *proc << "-a" << "-S";
00173 if (ksconfig->noRootAffix())
00174 *proc<<"-m";
00175
00176 if (ksconfig->runTogether())
00177 *proc << "-B";
00178 else
00179 *proc << "-C";
00180
00181 if (trystart<2)
00182 {
00183 if (! ksconfig->dictionary().isEmpty())
00184 {
00185 kdDebug(32500) << "using dictionary [" << ksconfig->dictionary() << "]" << endl;
00186 *proc << "-d";
00187 *proc << ksconfig->dictionary();
00188 }
00189 }
00190
00191
00192
00193
00194
00195
00196 if (trystart<1)
00197 switch (ksconfig->encoding())
00198 {
00199 case KS_E_LATIN1:
00200 *proc << "-Tlatin1";
00201 break;
00202 case KS_E_LATIN2:
00203 *proc << "-Tlatin2";
00204 break;
00205 case KS_E_LATIN3:
00206 *proc << "-Tlatin3";
00207 break;
00208
00209
00210 case KS_E_LATIN4:
00211 case KS_E_LATIN5:
00212 case KS_E_LATIN7:
00213 case KS_E_LATIN8:
00214 case KS_E_LATIN9:
00215 case KS_E_LATIN13:
00216 case KS_E_LATIN15:
00217
00218 kdError() << "charsets iso-8859-4 .. iso-8859-15 not supported yet" << endl;
00219 break;
00220
00221 case KS_E_UTF8:
00222 *proc << "-Tutf8";
00223 break;
00224
00225 case KS_E_KOI8U:
00226 *proc << "-w'";
00227 break;
00228 }
00229
00230 if(trystart==0)
00231 {
00232 connect(proc, SIGNAL (receivedStderr (KProcess *, char *, int)),
00233 this, SLOT (ispellErrors (KProcess *, char *, int)));
00234
00235 connect(proc, SIGNAL(processExited(KProcess *)),
00236 this, SLOT (ispellExit (KProcess *)));
00237
00238 OUTPUT(KoSpell2);
00239 }
00240
00241 if(!proc->start())
00242 {
00243 m_status = Error;
00244 QTimer::singleShot(0, this, SLOT(emitDeath()));
00245 }
00246 }
00247
00248 void KoSpell::ispellErrors(KProcess *, char *buffer, int buflen)
00249 {
00250 buffer [buflen-1] = '\0';
00251 kdDebug(32500) << "ispellErrors [" << buffer << "]\n" << endl;
00252 }
00253
00254 void KoSpell::KoSpell2 (KProcIO *)
00255 {
00256 kdDebug(32500) << "KoSpell::KoSpell2" << endl;
00257
00258 QString line;
00259
00260 if(proc->fgets(line, true)==-1)
00261 {
00262 QTimer::singleShot(0, this, SLOT(emitDeath()));
00263 return;
00264 }
00265
00266 if(line[0]!='@')
00267 {
00268 QTimer::singleShot(0, this, SLOT(emitDeath()));
00269 return;
00270 }
00271
00272
00273 proc->fputs("!");
00274
00275 NOOUTPUT (KoSpell2);
00276 OUTPUT(check2);
00277
00278 m_status = Running;
00279 emit ready(this);
00280 }
00281
00282 bool KoSpell::addPersonal(const QString & word)
00283 {
00284 QString w = word;
00285
00286
00287 if(w.find (' ')!=-1 || w.isEmpty())
00288 return false;
00289
00290 w.prepend ("*");
00291 w.append( "\n#" );
00292
00293 return proc->fputs(w);
00294 }
00295
00296 bool KoSpell::writePersonalDictionary()
00297 {
00298 return proc->fputs("#");
00299 }
00300
00301 bool KoSpell::ignore(const QString & word)
00302 {
00303 QString qs = word.simplifyWhiteSpace();
00304
00305
00306 if (qs.find (' ')!=-1 || qs.isEmpty())
00307 return FALSE;
00308
00309 qs.prepend ("@");
return proc->fputs(qs);
}
// composes a guess from ispell to a readable word
// e.g. "re+fry-y+ies" -> "refries"
00310
00311 QString KoSpell::funnyWord(const QString & word)
00312 {
00313 QString qs;
00314 unsigned int i=0;
00315
00316 for (i=0; i<word.length(); i++)
00317 {
00318 if (word[i]=='+')
00319 continue;
00320
00321 if (word [i]=='-')
00322 {
00323 QString shorty;
00324 unsigned int j;
00325 int k;
00326
00327 for(j=i+1; j<word.length() && word [j]!='+' && word [j]!='-'; j++)
00328 shorty+=word [j];
00329 i=j-1;
00330
00331 if ((k=qs.findRev (shorty))==0 || k!=-1)
00332 qs.remove (k,shorty.length());
00333 else
00334 {
00335 qs+='-';
00336 qs+=shorty;
00337 }
00338 }
00339 else
00340 qs+=word [i];
00341 }
00342 return qs;
00343 }
00344
00345 KoSpell::Spelling KoSpell::parseLine(const QString &line, QString &word, int &pos)
00346 {
00347 bool skip = false;
00348
00349
00350 if(line.isEmpty())
00351 return SpellingDone;
00352
00353 QChar ch = line[0];
00354 switch(ch)
00355 {
00356 case '*':
00357 case '+':
00358 case '-':
00359 return SpellingOk;
00360 case '&':
00361 case '?':
00362 skip = true;
00363 case '#':
00364 {
00365 int p = line.find(QChar(' '), 2);
00366 word = line.mid(2, p-2);
00367 p++;
00368 if(skip)
00369 {
00370 while(line[p].isDigit())
00371 p++;
00372 p++;
00373 }
00374 int l=0;
00375 while(line[p+l].isDigit())
00376 l++;
00377 bool ok=true;
00378 pos = line.mid(p,l).toInt(&ok);
00379
00380
00381
00382 return Misspelled;
00383 }
00384 default:
00385 return SpellingError;
00386 }
00387 return SpellingError;
00388 }
00389
00390 bool KoSpell::check(const QString &buffer)
00391 {
00392
00393 if(buffer.isEmpty())
00394 {
00395
00396 emit done();
00397 return true;
00398 }
00399
00400
00401 QString buf( buffer );
00402 buf.replace( '\n', ' ' );
00403
00404
00405 m_buffer << buf;
00406
00407 proc->fputs("^", false);
00408 proc->fputs(buf);
00409
00410 return true;
00411 }
00412
00413
00414 void KoSpell::check2(KProcIO *)
00415 {
00416
00417 QString line;
00418 int bytes;
00419 while((bytes=proc->fgets(line, true)) >= 0)
00420 {
00421
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432 int pos=0;
00433 QString word;
00434 Spelling spelling = parseLine(line, word, pos);
00435 if(word.length()>1 && d->m_bIgnoreTitleCase && word==word.upper())
00436 {
00437 spelling=SpellingIgnore;
00438 }
00439
00440 if(word.length()>1 && d->m_bIgnoreUpperWords && word[0]==word[0].upper())
00441 {
00442 QString text=word[0]+word.right(word.length()-1).lower();
00443 if(text==word)
00444 spelling=SpellingIgnore;
00445 }
00446 if (ignorelist.findIndex(word.lower())!=-1)
00447 spelling=SpellingIgnore;
00448
00449 switch(spelling)
00450 {
00451 case Misspelled:
00452 {
00453 QString buffer = m_buffer.front();
00454 pos--;
00455
00456
00457 if ( ksconfig->encoding() == KS_E_UTF8 )
00458 {
00459 for(int i=0; i < pos; i++)
00460 {
00461 ushort u = buffer[i].unicode();
00462 if(u > 0x7f)
00463 pos--;
00464 else if(u > 0x7ff)
00465 pos-=2;
00466
00467
00468
00469
00470 }
00471 }
00472
00473 emit misspelling(word, pos);
00474 break;
00475 }
00476
00477 case SpellingDone:
00478
00479 Q_ASSERT(!m_buffer.isEmpty());
00480 if (!m_buffer.isEmpty())
00481 m_buffer.pop_front();
00482 emit done();
00483 break;
00484 case SpellingIgnore:
00485 break;
00486 default:
00487 kdDebug(32500) << "KoSpell::check2() ERROR" << endl;
00488 break;
00489 }
00490 }
00491
00492
00493 }
00494
00495 KoSpell:: ~KoSpell ()
00496 {
00497 delete d;
00498 delete proc;
00499 delete ksconfig;
00500 }
00501
00502 KSpellConfig KoSpell::ksConfig () const
00503 {
00504 ksconfig->setIgnoreList(ignorelist);
00505 return *ksconfig;
00506 }
00507
00508 void KoSpell::cleanUp ()
00509 {
00510 if (m_status == Cleaning) return;
00511 if (m_status == Running)
00512 {
00513 m_status = Cleaning;
00514 }
00515 proc->closeStdin();
00516 }
00517
00518 void KoSpell::ispellExit(KProcess *)
00519 {
00520 kdDebug(32500) << "KoSpell::ispellExit() " << m_status << endl;
00521
00522 if ((m_status == Starting) && (trystart<maxtrystart))
00523 {
00524 trystart++;
00525 startIspell();
00526 return;
00527 }
00528
00529 if (m_status == Starting)
00530 m_status = Error;
00531 else if (m_status == Cleaning)
00532 m_status = Finished;
00533 else if (m_status == Running)
00534 m_status = Crashed;
00535 else
00536 return;
00537
00538 kdDebug(32500) << "Death" << endl;
00539 QTimer::singleShot( 0, this, SLOT(emitDeath()));
00540 }
00541
00542
00543
00544
00545 void KoSpell::emitDeath()
00546 {
00547
00548 emit death();
00549
00550
00551 }
00552
00553 void KoSpell::setIgnoreUpperWords(bool _ignore)
00554 {
00555 d->m_bIgnoreUpperWords=_ignore;
00556 }
00557
00558 void KoSpell::setIgnoreTitleCase(bool _ignore)
00559 {
00560 d->m_bIgnoreTitleCase=_ignore;
00561 }
00562
00563
00564
00565 QStringList KoSpell::getAvailDictsIspell ()
00566 {
00567 QStringList listIspell;
00568
00569 QFileInfo dir ("/usr/lib/ispell");
00570 if (!dir.exists() || !dir.isDir())
00571 dir.setFile ("/usr/local/lib/ispell");
00572 if (!dir.exists() || !dir.isDir())
00573 dir.setFile ("/usr/local/share/ispell");
00574 if (!dir.exists() || !dir.isDir())
00575 dir.setFile ("/usr/share/ispell");
00576
00577
00578
00579
00580
00581 if (!dir.exists() || !dir.isDir()) return QStringList();
00582
00583 kdDebug(32500) << "KoSpell::getAvailDictsIspell "
00584 << dir.filePath() << " " << dir.dirPath() << endl;
00585
00586 QDir thedir (dir.filePath(),"*.hash");
00587
00588 kdDebug(32500) << "KoSpell" << thedir.path() << "\n" << endl;
00589 kdDebug(32500) << "entryList().count()="
00590 << thedir.entryList().count() << endl;
00591
00592 for (unsigned int i=0;i<thedir.entryList().count();i++)
00593 {
00594 QString fname, lname, hname;
00595 fname = thedir [i];
00596
00597
00598 if (fname.right(5) == ".hash") fname.remove (fname.length()-5,5);
00599
00600 if (interpret (fname, lname, hname))
00601 {
00602 hname=i18n("default spelling dictionary"
00603 ,"Default - %1 [%2]").arg(hname).arg(fname);
00604 listIspell.append(hname);
00605 }
00606 else
00607 {
00608 hname=hname+" ["+fname+"]";
00609 listIspell.append(hname);
00610 }
00611 }
00612 return listIspell;
00613 }
00614
00615 QStringList KoSpell::getAvailDictsAspell () {
00616
00617 QStringList listAspell;
00618
00619
00620
00621 QFileInfo dir ("/usr/lib/aspell");
00622 if (!dir.exists() || !dir.isDir())
00623 dir.setFile ("/usr/local/lib/aspell");
00624 if (!dir.exists() || !dir.isDir())
00625 dir.setFile ("/usr/share/aspell");
00626 if (!dir.exists() || !dir.isDir())
00627 dir.setFile ("/usr/local/share/aspell");
00628 if (!dir.exists() || !dir.isDir()) return QStringList();
00629
00630 kdDebug(32500) << "KoSpell::getAvailDictsAspell "
00631 << dir.filePath() << " " << dir.dirPath() << endl;
00632
00633 QDir thedir (dir.filePath(),"*");
00634
00635 kdDebug(32500) << "KSpellConfig" << thedir.path() << "\n" << endl;
00636 kdDebug(32500) << "entryList().count()="
00637 << thedir.entryList().count() << endl;
00638
00639 for (unsigned int i=0; i<thedir.entryList().count(); i++)
00640 {
00641 QString fname, lname, hname;
00642 fname = thedir [i];
00643
00644
00645
00646
00647 if (fname[0] != '.' && fname.find('-') < 0)
00648 {
00649
00650
00651 if (fname.right(6) == ".multi") fname.remove (fname.length()-6,6);
00652
00653 if (interpret (fname, lname, hname))
00654 {
00655 hname=i18n("default spelling dictionary"
00656 ,"Default - %1 [%2]").arg(hname).arg(fname);
00657 listAspell.append(hname);
00658 }
00659 else
00660 {
00661 hname=hname+" ["+fname+"]";
00662 listAspell.append(hname);
00663 }
00664 }
00665 }
00666 return listAspell;
00667 }
00668
00669
00670 bool
00671 KoSpell::interpret (QString &fname, QString &lname,
00672 QString &hname)
00673
00674 {
00675
00676 kdDebug(750) << "KSpellConfig::interpret [" << fname << "]" << endl;
00677
00678 QString dname(fname);
00679
00680 if(dname.right(1)=="+")
00681 dname.remove(dname.length()-1, 1);
00682
00683 if(dname.right(3)=="sml" || dname.right(3)=="med" || dname.right(3)=="lrg" || dname.right(3)=="xlg")
00684 dname.remove(dname.length()-3,3);
00685
00686
00687 if (dname=="english" || dname=="american" ||
00688 dname=="british" || dname=="canadian") {
00689 lname="en"; hname=i18n("English");
00690 }
00691 else if (dname=="espa~nol" || dname=="espanol") {
00692 lname="es"; hname=i18n("Spanish");
00693 }
00694 else if (dname=="dansk") {
00695 lname="da"; hname=i18n("Danish");
00696 }
00697 else if (dname=="deutsch") {
00698 lname="de"; hname=i18n("German");
00699 }
00700 else if (dname=="german") {
00701 lname="de"; hname=i18n("German (new orth.)");
00702 }
00703 else if (dname=="portuguesb" || dname=="br") {
00704 lname="br"; hname=i18n("Brazilian Portuguese");
00705 }
00706 else if (dname=="portugues") {
00707 lname="pt"; hname=i18n("Portuguese");
00708 }
00709 else if (dname=="esperanto") {
00710 lname="eo"; hname=i18n("Esperanto");
00711 }
00712 else if (dname=="norsk") {
00713 lname="no"; hname=i18n("Norwegian");
00714 }
00715 else if (dname=="polish") {
00716 lname="pl"; hname=i18n("Polish");
00717 }
00718 else if (dname=="russian") {
00719 lname="ru"; hname=i18n("Russian");
00720 }
00721 else if (dname=="slovensko") {
00722 lname="si"; hname=i18n("Slovenian");
00723 }
00724 else if (dname=="slovak"){
00725 lname="sk"; hname=i18n("Slovak");
00726 }
00727 else if (dname=="czech") {
00728 lname="cs"; hname=i18n("Czech");
00729 }
00730 else if (dname=="svenska") {
00731 lname="sv"; hname=i18n("Swedish");
00732 }
00733 else if (dname=="swiss") {
00734 lname="de"; hname=i18n("Swiss German");
00735 }
00736 else if (dname=="ukrainian") {
00737 lname="uk"; hname=i18n("Ukrainian");
00738 }
00739 else if (dname=="lietuviu" || dname=="lithuanian") {
00740 lname="lt"; hname=i18n("Lithuanian");
00741 }
00742 else if (dname=="francais" || dname=="french") {
00743 lname="fr"; hname=i18n("French");
00744 }
00745 else if (dname=="belarusian") {
00746 lname="be"; hname=i18n("Belarusian");
00747 }
00748 else if( dname == "magyar" ) {
00749 lname="hu"; hname=i18n("Hungarian");
00750 }
00751 else {
00752 lname=""; hname=i18n("Unknown ispell dictionary", "Unknown");
00753 }
00754
00755
00756 if ( (KGlobal::locale()->language()==QString::fromLatin1("C") &&
00757 lname==QString::fromLatin1("en")) ||
00758 KGlobal::locale()->language()==lname)
00759 return TRUE;
00760
00761 return FALSE;
00762 }
00763
00764
00765 #include "kospell.moc"
00766
00767