filter.cpp

00001 // -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
00002 /*
00003  * filter.cpp
00004  *
00005  * Copyright (C)  2004  Zack Rusin <zack@kde.org>
00006  *
00007  * This library is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU Lesser General Public
00009  * License as published by the Free Software Foundation; either
00010  * version 2.1 of the License, or (at your option) any later version.
00011  *
00012  * This library is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015  * Lesser General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU Lesser General Public
00018  * License along with this library; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00020  * 02110-1301  USA
00021  */
00022 
00023 #include "filter.h"
00024 
00025 #include "settings.h"
00026 
00027 #include <kstaticdeleter.h>
00028 #include <kdebug.h>
00029 
00030 #include <qstring.h>
00031 
00032 namespace KSpell2
00033 {
00034 
00035 static Word endWord;
00036 static KStaticDeleter<Filter> sd;
00037 static Filter* defFilter = 0;
00038 
00039 class Filter::Private
00040 {
00041 public:
00042     // The reason it's not in the class directly is that
00043     // i'm not 100% sure that having the settings() here is
00044     // the way i want to be doing this.
00045     Settings *settings;
00046 };
00047 
00048 Filter* Filter::defaultFilter()
00049 {
00050     if ( !defFilter )
00051         sd.setObject( defFilter, new Filter() );
00052     return defFilter;
00053 }
00054 
00055 Word Filter::end()
00056 {
00057     return endWord;
00058 }
00059 
00060 Filter::Filter()
00061     : m_currentPosition( 0 )
00062 {
00063     d = new Private;
00064     d->settings = 0;
00065 }
00066 
00067 Filter::~Filter()
00068 {
00069     delete d; d = 0;
00070 }
00071 
00072 void Filter::setSettings( Settings *conf )
00073 {
00074     d->settings = conf;
00075 }
00076 
00077 Settings *Filter::settings() const
00078 {
00079     return d->settings;
00080 }
00081 
00082 void Filter::restart()
00083 {
00084     m_currentPosition = 0;
00085 }
00086 
00087 void Filter::setBuffer( const QString& buffer )
00088 {
00089     m_buffer          = buffer;
00090     m_currentPosition = 0;
00091 }
00092 
00093 QString Filter::buffer() const
00094 {
00095     return m_buffer;
00096 }
00097 
00098 bool Filter::atEnd() const
00099 {
00100     if ( m_currentPosition >= m_buffer.length() ) {
00101         return true;
00102     } else
00103         return false;
00104 }
00105 
00106 Word Filter::nextWord() const
00107 {
00108     QChar currentChar = skipToLetter( m_currentPosition );
00109 
00110     if ( m_currentPosition >= m_buffer.length() ) {
00111         return Filter::end();
00112     }
00113 
00114     bool allUppercase = currentChar.category() & QChar::Letter_Uppercase;
00115     bool runTogether = false;
00116 
00117     QString foundWord;
00118     int start = m_currentPosition;
00119     while ( currentChar.isLetter() ) {
00120         if ( currentChar.category() & QChar::Letter_Lowercase )
00121             allUppercase = false;
00122 
00123     /* FIXME: this does not work for Hebrew for example
00124         //we consider run-together words as mixed-case words
00125         if ( !allUppercase &&
00126              currentChar.category() & QChar::Letter_Uppercase )
00127             runTogether = true;
00128     */
00129 
00130         foundWord += currentChar;
00131         ++m_currentPosition;
00132         currentChar = m_buffer[ m_currentPosition ];
00133     }
00134 
00135     if ( shouldBeSkipped( allUppercase, runTogether, foundWord ) )
00136         return nextWord();
00137 
00138     return Word( foundWord, start );
00139 }
00140 
00141 Word Filter::previousWord() const
00142 {
00143     while ( !m_buffer[ m_currentPosition ].isLetter() &&
00144             m_currentPosition != 0) {
00145         --m_currentPosition;
00146     }
00147 
00148     if ( m_currentPosition == 0 ) {
00149         return Filter::end();
00150     }
00151 
00152     QString foundWord;
00153     int start = m_currentPosition;
00154     while ( m_buffer[ start ].isLetter() ) {
00155         foundWord.prepend( m_buffer[ m_currentPosition ] );
00156         --start;
00157     }
00158 
00159     return Word( foundWord, start );
00160 }
00161 
00162 Word Filter::wordAtPosition( unsigned int pos ) const
00163 {
00164     if ( pos > m_buffer.length() )
00165         return Filter::end();
00166 
00167     int currentPosition = pos - 1;
00168     QString foundWord;
00169     while ( currentPosition >= 0 &&
00170             m_buffer[ currentPosition ].isLetter() ) {
00171         foundWord.prepend( m_buffer[ currentPosition ] );
00172         --currentPosition;
00173     }
00174 
00175     // currentPosition == 0 means the first char is not letter
00176     // currentPosition == -1 means we reached the beginning
00177     int start = (currentPosition < 0) ? 0 : ++currentPosition;
00178     currentPosition = pos ;
00179     if ( m_buffer[ currentPosition ].isLetter() ) {
00180         while ( m_buffer[ currentPosition ].isLetter() ) {
00181             foundWord.append( m_buffer[ currentPosition ] );
00182             ++currentPosition;
00183         }
00184     }
00185 
00186     return Word( foundWord, start );
00187 }
00188 
00189 
00190 void Filter::setCurrentPosition( int i )
00191 {
00192     m_currentPosition = i;
00193 
00194     //go back to the last word so that next word returns something
00195     //useful
00196     while ( m_buffer[m_currentPosition].isLetter() && m_currentPosition > 0 )
00197         --m_currentPosition;
00198 }
00199 
00200 int Filter::currentPosition() const
00201 {
00202     return m_currentPosition;
00203 }
00204 
00205 void Filter::replace( const Word& w, const QString& newWord)
00206 {
00207     int oldLen = w.word.length();
00208     int newLen = newWord.length();
00209 
00210     if ( oldLen != newLen && m_currentPosition > w.start ) {
00211         if ( m_currentPosition > w.start ) {
00212             int len = newLen - oldLen;
00213             m_currentPosition += len;
00214         }
00215     }
00216     m_buffer = m_buffer.replace( w.start, oldLen, newWord );
00217 }
00218 
00219 QString Filter::context() const
00220 {
00221     int len = 60;
00222     //we don't want the expression underneath casted to an unsigned int
00223     //which would cause it to always evaluate to false
00224     int signedPosition = m_currentPosition;
00225     bool begin = ( (signedPosition - len/2)<=0 ) ? true : false;
00226 
00227 
00228     QString buffer = m_buffer;
00229     Word word = wordAtPosition( m_currentPosition );
00230     buffer = buffer.replace( word.start, word.word.length(),
00231                              QString( "<b>%1</b>" ).arg( word.word ) );
00232 
00233     QString context;
00234     if ( begin )
00235         context = QString( "%1...")
00236                   .arg( buffer.mid(  0, len ) );
00237     else
00238         context = QString( "...%1..." )
00239                   .arg( buffer.mid(  m_currentPosition - 20, len ) );
00240 
00241     context = context.replace( '\n', ' ' );
00242 
00243     return context;
00244 }
00245 
00246 bool Filter::trySkipLinks() const
00247 {
00248     QChar currentChar = m_buffer[ m_currentPosition ];
00249 
00250     uint length = m_buffer.length();
00251     //URL - if so skip
00252     if ( currentChar == ':' &&
00253          ( m_buffer[ ++m_currentPosition] == '/' || ( m_currentPosition + 1 ) >= length ) ) {
00254         //in both cases url is considered finished at the first whitespace occurence
00255         while ( !m_buffer[ m_currentPosition++ ].isSpace() && m_currentPosition < length )
00256             ;
00257         return true;
00258     }
00259 
00260     //Email - if so skip
00261     if ( currentChar == '@' ) {
00262         while ( !m_buffer[ ++m_currentPosition ].isSpace() && m_currentPosition < length )
00263             ;
00264         return true;
00265     }
00266 
00267     return false;
00268 }
00269 
00270 bool Filter::ignore( const QString& word ) const
00271 {
00272     if ( d->settings ) {
00273         return d->settings->ignore( word );
00274     }
00275     return false;
00276 }
00277 
00278 QChar Filter::skipToLetter( uint &fromPosition ) const
00279 {
00280 
00281     QChar currentChar = m_buffer[ fromPosition ];
00282     while ( !currentChar.isLetter() &&
00283             ++fromPosition < m_buffer.length() ) {
00284         currentChar = m_buffer[ fromPosition ];
00285     }
00286     return currentChar;
00287 }
00288 
00289 bool Filter::shouldBeSkipped( bool wordWasUppercase, bool wordWasRunTogether,
00290                              const QString& foundWord ) const
00291 {
00292     bool checkUpper = ( d->settings ) ?
00293                       d->settings->checkUppercase () : true;
00294     bool skipRunTogether = ( d->settings ) ?
00295                            d->settings->skipRunTogether() : true;
00296 
00297     if ( trySkipLinks() )
00298         return true;
00299 
00300     if ( wordWasUppercase && !checkUpper )
00301         return true;
00302 
00303     if ( wordWasRunTogether && skipRunTogether )
00304         return true;
00305 
00306     return ignore( foundWord );
00307 }
00308 
00309 }
KDE Home | KDE Accessibility Home | Description of Access Keys