|
|
|
// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
|
|
|
|
/*
|
|
|
|
* filter.cpp
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004 Zack Rusin <zack@kde.org>
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
* 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "filter.h"
|
|
|
|
|
|
|
|
#include "settings.h"
|
|
|
|
|
|
|
|
#include <kstaticdeleter.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
#include <tqstring.h>
|
|
|
|
|
|
|
|
namespace KSpell2
|
|
|
|
{
|
|
|
|
|
|
|
|
static Word endWord;
|
|
|
|
static KStaticDeleter<Filter> sd;
|
|
|
|
static Filter* defFilter = 0;
|
|
|
|
|
|
|
|
class Filter::Private
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
// The reason it's not in the class directly is that
|
|
|
|
// i'm not 100% sure that having the settings() here is
|
|
|
|
// the way i want to be doing this.
|
|
|
|
Settings *settings;
|
|
|
|
};
|
|
|
|
|
|
|
|
Filter* Filter::defaultFilter()
|
|
|
|
{
|
|
|
|
if ( !defFilter )
|
|
|
|
sd.setObject( defFilter, new Filter() );
|
|
|
|
return defFilter;
|
|
|
|
}
|
|
|
|
|
|
|
|
Word Filter::end()
|
|
|
|
{
|
|
|
|
return endWord;
|
|
|
|
}
|
|
|
|
|
|
|
|
Filter::Filter()
|
|
|
|
: m_currentPosition( 0 )
|
|
|
|
{
|
|
|
|
d = new Private;
|
|
|
|
d->settings = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Filter::~Filter()
|
|
|
|
{
|
|
|
|
delete d; d = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Filter::setSettings( Settings *conf )
|
|
|
|
{
|
|
|
|
d->settings = conf;
|
|
|
|
}
|
|
|
|
|
|
|
|
Settings *Filter::settings() const
|
|
|
|
{
|
|
|
|
return d->settings;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Filter::restart()
|
|
|
|
{
|
|
|
|
m_currentPosition = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Filter::setBuffer( const TQString& buffer )
|
|
|
|
{
|
|
|
|
m_buffer = buffer;
|
|
|
|
m_currentPosition = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Filter::buffer() const
|
|
|
|
{
|
|
|
|
return m_buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Filter::atEnd() const
|
|
|
|
{
|
|
|
|
if ( m_currentPosition >= m_buffer.length() ) {
|
|
|
|
return true;
|
|
|
|
} else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Word Filter::nextWord() const
|
|
|
|
{
|
|
|
|
TQChar currentChar = skipToLetter( m_currentPosition );
|
|
|
|
|
|
|
|
if ( m_currentPosition >= m_buffer.length() ) {
|
|
|
|
return Filter::end();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool allUppercase = currentChar.category() & TQChar::Letter_Uppercase;
|
|
|
|
bool runTogether = false;
|
|
|
|
|
|
|
|
TQString foundWord;
|
|
|
|
int start = m_currentPosition;
|
|
|
|
while ( currentChar.isLetter() ) {
|
|
|
|
if ( currentChar.category() & TQChar::Letter_Lowercase )
|
|
|
|
allUppercase = false;
|
|
|
|
|
|
|
|
/* FIXME: this does not work for Hebrew for example
|
|
|
|
//we consider run-together words as mixed-case words
|
|
|
|
if ( !allUppercase &&
|
|
|
|
currentChar.category() & TQChar::Letter_Uppercase )
|
|
|
|
runTogether = true;
|
|
|
|
*/
|
|
|
|
|
|
|
|
foundWord += currentChar;
|
|
|
|
++m_currentPosition;
|
|
|
|
currentChar = m_buffer[ m_currentPosition ];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( shouldBeSkipped( allUppercase, runTogether, foundWord ) )
|
|
|
|
return nextWord();
|
|
|
|
|
|
|
|
return Word( foundWord, start );
|
|
|
|
}
|
|
|
|
|
|
|
|
Word Filter::previousWord() const
|
|
|
|
{
|
|
|
|
while ( !m_buffer[ m_currentPosition ].isLetter() &&
|
|
|
|
m_currentPosition != 0) {
|
|
|
|
--m_currentPosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( m_currentPosition == 0 ) {
|
|
|
|
return Filter::end();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString foundWord;
|
|
|
|
int start = m_currentPosition;
|
|
|
|
while ( m_buffer[ start ].isLetter() ) {
|
|
|
|
foundWord.prepend( m_buffer[ m_currentPosition ] );
|
|
|
|
--start;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Word( foundWord, start );
|
|
|
|
}
|
|
|
|
|
|
|
|
Word Filter::wordAtPosition( unsigned int pos ) const
|
|
|
|
{
|
|
|
|
if ( pos > m_buffer.length() )
|
|
|
|
return Filter::end();
|
|
|
|
|
|
|
|
int currentPosition = pos - 1;
|
|
|
|
TQString foundWord;
|
|
|
|
while ( currentPosition >= 0 &&
|
|
|
|
m_buffer[ currentPosition ].isLetter() ) {
|
|
|
|
foundWord.prepend( m_buffer[ currentPosition ] );
|
|
|
|
--currentPosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
// currentPosition == 0 means the first char is not letter
|
|
|
|
// currentPosition == -1 means we reached the beginning
|
|
|
|
int start = (currentPosition < 0) ? 0 : ++currentPosition;
|
|
|
|
currentPosition = pos ;
|
|
|
|
if ( m_buffer[ currentPosition ].isLetter() ) {
|
|
|
|
while ( m_buffer[ currentPosition ].isLetter() ) {
|
|
|
|
foundWord.append( m_buffer[ currentPosition ] );
|
|
|
|
++currentPosition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Word( foundWord, start );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Filter::setCurrentPosition( int i )
|
|
|
|
{
|
|
|
|
m_currentPosition = i;
|
|
|
|
|
|
|
|
//go back to the last word so that next word returns something
|
|
|
|
//useful
|
|
|
|
while ( m_buffer[m_currentPosition].isLetter() && m_currentPosition > 0 )
|
|
|
|
--m_currentPosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Filter::currentPosition() const
|
|
|
|
{
|
|
|
|
return m_currentPosition;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Filter::replace( const Word& w, const TQString& newWord)
|
|
|
|
{
|
|
|
|
int oldLen = w.word.length();
|
|
|
|
int newLen = newWord.length();
|
|
|
|
|
|
|
|
if ( oldLen != newLen && m_currentPosition > w.start ) {
|
|
|
|
if ( m_currentPosition > w.start ) {
|
|
|
|
int len = newLen - oldLen;
|
|
|
|
m_currentPosition += len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_buffer = m_buffer.replace( w.start, oldLen, newWord );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Filter::context() const
|
|
|
|
{
|
|
|
|
int len = 60;
|
|
|
|
//we don't want the expression underneath casted to an unsigned int
|
|
|
|
//which would cause it to always evaluate to false
|
|
|
|
int signedPosition = m_currentPosition;
|
|
|
|
bool begin = ( (signedPosition - len/2)<=0 ) ? true : false;
|
|
|
|
|
|
|
|
|
|
|
|
TQString buffer = m_buffer;
|
|
|
|
Word word = wordAtPosition( m_currentPosition );
|
|
|
|
buffer = buffer.replace( word.start, word.word.length(),
|
|
|
|
TQString( "<b>%1</b>" ).arg( word.word ) );
|
|
|
|
|
|
|
|
TQString context;
|
|
|
|
if ( begin )
|
|
|
|
context = TQString( "%1...")
|
|
|
|
.arg( buffer.mid( 0, len ) );
|
|
|
|
else
|
|
|
|
context = TQString( "...%1..." )
|
|
|
|
.arg( buffer.mid( m_currentPosition - 20, len ) );
|
|
|
|
|
|
|
|
context = context.replace( '\n', ' ' );
|
|
|
|
|
|
|
|
return context;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Filter::trySkipLinks() const
|
|
|
|
{
|
|
|
|
TQChar currentChar = m_buffer[ m_currentPosition ];
|
|
|
|
|
|
|
|
uint length = m_buffer.length();
|
|
|
|
//URL - if so skip
|
|
|
|
if ( currentChar == ':' &&
|
|
|
|
( m_buffer[ ++m_currentPosition] == '/' || ( m_currentPosition + 1 ) >= length ) ) {
|
|
|
|
//in both cases url is considered finished at the first whitespace occurence
|
|
|
|
while ( !m_buffer[ m_currentPosition++ ].isSpace() && m_currentPosition < length )
|
|
|
|
;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Email - if so skip
|
|
|
|
if ( currentChar == '@' ) {
|
|
|
|
while ( !m_buffer[ ++m_currentPosition ].isSpace() && m_currentPosition < length )
|
|
|
|
;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Filter::ignore( const TQString& word ) const
|
|
|
|
{
|
|
|
|
if ( d->settings ) {
|
|
|
|
return d->settings->ignore( word );
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQChar Filter::skipToLetter( uint &fromPosition ) const
|
|
|
|
{
|
|
|
|
|
|
|
|
TQChar currentChar = m_buffer[ fromPosition ];
|
|
|
|
while ( !currentChar.isLetter() &&
|
|
|
|
++fromPosition < m_buffer.length() ) {
|
|
|
|
currentChar = m_buffer[ fromPosition ];
|
|
|
|
}
|
|
|
|
return currentChar;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Filter::shouldBeSkipped( bool wordWasUppercase, bool wordWasRunTogether,
|
|
|
|
const TQString& foundWord ) const
|
|
|
|
{
|
|
|
|
bool checkUpper = ( d->settings ) ?
|
|
|
|
d->settings->checkUppercase () : true;
|
|
|
|
bool skipRunTogether = ( d->settings ) ?
|
|
|
|
d->settings->skipRunTogether() : true;
|
|
|
|
|
|
|
|
if ( trySkipLinks() )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if ( wordWasUppercase && !checkUpper )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if ( wordWasRunTogether && skipRunTogether )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return ignore( foundWord );
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|