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 }