Files
Mozilla/mozilla/extensions/spellcheck/src/nsSpellCheckController.cpp
gerv%gerv.net d6544e324a Bug 236613: change to MPL/LGPL/GPL tri-license.
git-svn-id: svn://10.0.0.236/trunk@155092 18797224-902f-48f8-a5cc-f745e15eee43
2004-04-18 22:14:17 +00:00

639 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla browser.
*
* The Initial Developer of the Original Code is
* Netscape Communications, Inc.
* Portions created by the Initial Developer are Copyright (C) 1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsSpellCheckController.h"
#include "nsIServiceManager.h"
#include "nsIDOMRange.h"
#include "nsISelection.h"
#include "nsIDOMRange.h"
#define NBSP_SPACE_CODE ((PRUnichar)32)
nsSpellCheckController::nsSpellCheckController() :
mOffset(0),
mEndPoint(0)
{
}
nsSpellCheckController::~nsSpellCheckController()
{
}
// #define DEBUG_SPELLCHECKGLUE_REFCNT 1
#ifdef DEBUG_SPELLCHECKGLUE_REFCNT
nsrefcnt nsSpellCheckController::AddRef(void)
{
return ++mRefCnt;
}
nsrefcnt nsSpellCheckController::Release(void)
{
NS_PRECONDITION(0 != mRefCnt, "dup release");
if (--mRefCnt == 0) {
NS_DELETEXPCOM(this);
return 0;
}
return mRefCnt;
}
NS_IMPL_QUERY_INTERFACE1(nsSpellCheckController, nsISpellChecker)
#else
NS_IMPL_ISUPPORTS1(nsSpellCheckController, nsISpellCheckController)
#endif
nsresult
NS_NewSpellCheckGlue(nsSpellCheckController** result)
{
NS_ENSURE_ARG_POINTER(result);
nsSpellCheckController* spellCheckGlue = new nsSpellCheckController();
NS_ENSURE_TRUE(spellCheckGlue, NS_ERROR_NULL_POINTER);
*result = spellCheckGlue;
NS_ADDREF(*result);
return NS_OK;
}
//---------------------------------------------------------------------
// aInitialRange can be NULL
//
NS_IMETHODIMP
nsSpellCheckController::Init(nsITextServicesDocument* aDoc, nsIDOMRange* aInitialRange)
{
NS_ENSURE_ARG_POINTER(aDoc);
// Create INSO Spell Checker
nsresult rv;
mSpellChecker = do_GetService(NS_SPELLCHECKER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Create WordBreaker
rv = nsSpellCheckUtils::GetWordBreaker(getter_AddRefs(mWordBreaker));
NS_ENSURE_SUCCESS(rv, rv);
// zero out offset before starting
mOffset = 0;
return SetDocument(aDoc, aInitialRange);
}
nsresult
nsSpellCheckController::SetDocument(nsITextServicesDocument *aDoc,nsIDOMRange* aInitialRange)
{
NS_ENSURE_ARG_POINTER(aDoc);
// XXX: Modify this method so that it can be called
// more than once with a different aDoc. This will
// allow us to reuse a spellchecker.
mDocument = do_QueryInterface(aDoc);
NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
nsresult rv;
if (!aInitialRange) {
rv = mDocument->FirstBlock();
} else {
rv = aInitialRange->GetStartContainer(getter_AddRefs(mStartNode));
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 startOffset;
rv = aInitialRange->GetStartOffset(&startOffset);
NS_ENSURE_SUCCESS(rv, rv);
mOffset = PRUint32(startOffset);
rv = aInitialRange->GetEndContainer(getter_AddRefs(mEndNode));
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 endOffset;
rv = aInitialRange->GetEndOffset(&endOffset);
NS_ENSURE_SUCCESS(rv, rv);
mEndPoint = PRUint32(endOffset);
nsITextServicesDocument::TSDBlockSelectionStatus selStatus;
PRInt32 selOffset, selLength;
rv = mDocument->FirstSelectedBlock(&selStatus, &selOffset, &selLength);
}
NS_ENSURE_SUCCESS(rv, rv);
rv = nsSpellCheckUtils::LoadTextBlockIntoBuffer(mDocument, mSpellChecker, mBlockBuffer, mText, mOffset);
NS_ENSURE_SUCCESS(rv, rv);
if (!aInitialRange) {
mOffset = 0;
mEndPoint = mText.Length() - 1;
} else {
rv = FindBeginningOfWord(mOffset);
NS_ENSURE_SUCCESS(rv, rv);
if (mStartNode == mEndNode) {
rv = FindBeginningOfWord(mEndPoint);
}
}
return rv;
}
nsresult
nsSpellCheckController::FindBeginningOfWord(PRUint32& aPos)
{
const PRUnichar* text = mText.get();
PRUint32 textLen = mText.Length();
PRUint32 wlen = 0;
PRUint32 beginWord = 0;
PRUint32 endWord = 0;
PRUint32 prvWordEnd;
PRUint32 offset = 0;
while (offset < textLen) {
prvWordEnd = endWord;
nsresult rv = mWordBreaker->FindWord(text, textLen, offset, &beginWord, &endWord);
if (NS_FAILED(rv)) break;
// The wordBreaker hands back the spaces inbetween the words
// so we need to skip any words that are all spaces
const PRUnichar* start = (text+offset);
const PRUnichar* endPtr = (text+endWord);
while (*start == NBSP_SPACE_CODE && start < endPtr)
start++;
if (start == endPtr) {
offset = endWord;
continue;
}
offset = endWord+1;
wlen = endWord - beginWord;
if (endWord < aPos) {
continue;
}
if (aPos >= beginWord) {
aPos = beginWord;
break;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsSpellCheckController::GetSpellChecker(nsISpellChecker **aSpellChecker)
{
NS_ENSURE_ARG_POINTER(aSpellChecker);
*aSpellChecker = mSpellChecker;
NS_IF_ADDREF(*aSpellChecker);
return NS_OK;
}
NS_IMETHODIMP
nsSpellCheckController::NextMisspelledWord(PRUnichar **aWord)
{
NS_ENSURE_ARG_POINTER(aWord);
NS_ENSURE_TRUE(mDocument && mSpellChecker && mWordBreaker, NS_ERROR_NULL_POINTER);
// Init the return values.
*aWord = nsnull;
nsCOMPtr<nsIDOMNode> currentNode;
mDocument->GetCurrentNode(getter_AddRefs(currentNode));
// There might not have been any text in the
// document. Just return NS_OK;
if (mText.IsEmpty() || (mOffset >= mEndPoint && currentNode == mEndNode)) {
return NS_OK;
}
nsresult result;
// Now get the next misspelled word in the document.
const PRUnichar* text = mText.get();
PRUint32 textLen = mText.Length();
PRUint32 wlen = 0;
PRUint32 beginWord = 0;
PRUint32 endWord = 0;
PRBool isMisspelled = PR_FALSE;
while (!isMisspelled) {
PRUnichar* word;
result = FindNextMisspelledWord(text, textLen, mOffset, wlen,
beginWord, endWord, word, PR_TRUE, isMisspelled);
if (NS_FAILED(result) || (beginWord > mEndPoint && currentNode == mEndNode)) {
if (word) nsMemory::Free(word);
return result;
}
if (isMisspelled) {
*aWord = word;
} else {
// No more misspelled words in the current buffer.
// Load another text block into the buffer, try again.
result = mDocument->NextBlock();
NS_ENSURE_SUCCESS(result, result);
PRBool isDone;
result = mDocument->IsDone(&isDone);
NS_ENSURE_SUCCESS(result, result);
if (isDone) {
// No more blocks to process. We're done, so just
// return OK for the result.
return NS_OK;
}
result = nsSpellCheckUtils::LoadTextBlockIntoBuffer(mDocument, mSpellChecker, mBlockBuffer, mText, mOffset);
NS_ENSURE_SUCCESS(result, result);
mDocument->GetCurrentNode(getter_AddRefs(currentNode));
if (currentNode == mEndNode) {
FindBeginningOfWord(mEndPoint);
}
mOffset = 0;
wlen = 0;
}
}
// We have a misspelled word. Select it in the document,
// and scroll the selection into view.
// XXX: SetSelection() no longer scrolls the selection
// into view. Something changed underneath the hood
// in the presentation shell. Need to fix this!
result = mDocument->SetSelection(beginWord, wlen);
NS_ENSURE_SUCCESS(result, result);
result = mDocument->ScrollSelectionIntoView();
NS_ENSURE_SUCCESS(result, result);
return result;
}
NS_IMETHODIMP
nsSpellCheckController::CheckWord(const PRUnichar *aWord, PRBool *aIsMisspelled)
{
NS_ENSURE_ARG_POINTER(aWord);
NS_ENSURE_ARG_POINTER(aIsMisspelled);
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
// start by assuming everything is spelled correctly.
*aIsMisspelled = PR_FALSE;
return mSpellChecker->Check(aWord, aIsMisspelled);
}
NS_IMETHODIMP
nsSpellCheckController::Replace(const PRUnichar *aOldWord, const PRUnichar *aNewWord, PRBool aAllOccurrences)
{
NS_ENSURE_ARG_POINTER(aOldWord);
NS_ENSURE_ARG_POINTER(aNewWord);
NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
nsresult result;
// XXX: To make this method work for a non-modal dialog, we will
// have to add code that compares the current selection with
// aOldWord.
if (aAllOccurrences) {
nsString oldWord(aOldWord);
nsString newWord(aNewWord);
result = ReplaceAll(&oldWord, &newWord);
} else if (!*aNewWord) {
result = mDocument->DeleteSelection();
} else {
nsString newWord(aNewWord);
result = mDocument->InsertText(&newWord);
}
return result;
}
nsresult
nsSpellCheckController::ReplaceAll(const nsString *aOldWord, const nsString *aNewWord)
{
NS_ENSURE_ARG_POINTER(aOldWord);
NS_ENSURE_ARG_POINTER(aNewWord);
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
nsresult result;
CharBuffer oldWord;
nsCOMPtr<nsIUnicodeEncoder> unicodeEncoder;
nsXPIDLString charSet;
result = mSpellChecker->GetCharset(getter_Copies(charSet));
if (NS_SUCCEEDED(result)) {
nsCOMPtr<nsIUnicodeDecoder> unicodeDecoder;
result = nsSpellCheckUtils::CreateUnicodeConverters(charSet,
getter_AddRefs(unicodeEncoder),
getter_AddRefs(unicodeDecoder));
NS_ENSURE_TRUE(unicodeEncoder, NS_ERROR_NULL_POINTER);
}
result = nsSpellCheckUtils::ReadStringIntoBuffer(unicodeEncoder, aOldWord, &oldWord);
NS_ENSURE_SUCCESS(result, result);
// Skip the current occurrence of old word. It will be the last
// occurrence we replace so that we can figure out where we were
// before replace all started!
result = ReplaceAllOccurrences(&oldWord, aNewWord);
NS_ENSURE_SUCCESS(result, result);
PRInt32 i, numBlocksBefore = 0;
PRBool isDone = PR_FALSE;
// Count the number of text blocks that came before the current
// one, so that we know when to stop replacing after we hit the
// end of the document and swing around to the beginning of the
// document.
while (!isDone) {
++numBlocksBefore;
result = mDocument->PrevBlock();
NS_ENSURE_SUCCESS(result, result);
result = mDocument->IsDone(&isDone);
NS_ENSURE_SUCCESS(result, result);
}
// Now reset the document so that we start replacing all occurrences
// between the current block and the end of the document!
result = mDocument->FirstBlock();
NS_ENSURE_SUCCESS(result, result);
i = numBlocksBefore;
while (i-- > 0) {
result = mDocument->NextBlock();
NS_ENSURE_SUCCESS(result, result);
}
// Now replace all occurrences till we hit the end of the document!
result = mDocument->IsDone(&isDone);
NS_ENSURE_SUCCESS(result, result);
while (!isDone) {
result = nsSpellCheckUtils::LoadTextBlockIntoBuffer(mDocument, mSpellChecker, mBlockBuffer, mText, mOffset);
NS_ENSURE_SUCCESS(result, result);
result = ReplaceAllOccurrences(&oldWord, aNewWord);
NS_ENSURE_SUCCESS(result, result);
result = mDocument->NextBlock();
NS_ENSURE_SUCCESS(result, result);
result = mDocument->IsDone(&isDone);
NS_ENSURE_SUCCESS(result, result);
}
// Now swing around to the beginning of the document, and
// replace all occurrences till we hit the old current block!
result = mDocument->FirstBlock();
NS_ENSURE_SUCCESS(result, result);
isDone = PR_FALSE;
while (numBlocksBefore-- > 0 && !isDone) {
result = nsSpellCheckUtils::LoadTextBlockIntoBuffer(mDocument, mSpellChecker, mBlockBuffer, mText, mOffset);
NS_ENSURE_SUCCESS(result, result);
result = ReplaceAllOccurrences(&oldWord, aNewWord);
NS_ENSURE_SUCCESS(result, result);
if (numBlocksBefore > 0) {
result = mDocument->NextBlock();
NS_ENSURE_SUCCESS(result, result);
// Track isDone because I'm paranoid!
result = mDocument->IsDone(&isDone);
NS_ENSURE_SUCCESS(result, result);
}
}
// Now reload the text into the buffer so that the
// spelling checker is reset to look for the next
// misspelled word.
result = nsSpellCheckUtils::LoadTextBlockIntoBuffer(mDocument, mSpellChecker, mBlockBuffer, mText, mOffset);
NS_ENSURE_SUCCESS(result, result);
return NS_OK;
}
nsresult
nsSpellCheckController::ReplaceAllOccurrences(const CharBuffer *aOldWord, const nsString *aNewWord)
{
NS_ENSURE_ARG_POINTER(aOldWord);
NS_ENSURE_ARG_POINTER(aNewWord);
NS_ENSURE_TRUE(mSpellChecker && mWordBreaker, NS_ERROR_NULL_POINTER);
// Now get the next misspelled word in the document.
const PRUnichar* text = mText.get();
PRUint32 textLen = mText.Length();
PRUint32 wlen = 0;
PRUint32 beginWord = 0;
PRUint32 endWord = 0;
PRUint32 offset = 0;
PRBool isMisspelled = PR_TRUE;
while (isMisspelled) {
PRUnichar* word;
nsresult result = FindNextMisspelledWord(text, textLen, offset, wlen,
beginWord, endWord, word, PR_FALSE, isMisspelled);
NS_ENSURE_SUCCESS(result, result);
if (isMisspelled) {
mWordBuffer.AssureCapacity(wlen + 1);
PRUint32 i;
for (i = 0; i < wlen; i++)
mWordBuffer.mData[i] = mBlockBuffer.mData[i + beginWord];
mWordBuffer.mData[i] = '\0';
mWordBuffer.mDataLength = wlen;
// XXX: Need to do a case insensitive comparison, and replace
// with a word that matches the misspelled word's caps.
if (aOldWord->mDataLength == mWordBuffer.mDataLength &&
!memcmp(aOldWord->mData, mWordBuffer.mData, aOldWord->mDataLength)) {
// We found an occurrence of old word, so replace it!
result = mDocument->SetSelection(beginWord, wlen);
NS_ENSURE_SUCCESS(result, result);
result = mDocument->ScrollSelectionIntoView();
NS_ENSURE_SUCCESS(result, result);
if (aNewWord->Length() > 0)
result = mDocument->InsertText(aNewWord);
else
result = mDocument->DeleteSelection();
NS_ENSURE_SUCCESS(result, result);
offset = endWord;
}
}
}
return NS_OK;
}
NS_IMETHODIMP
nsSpellCheckController::SpellCheckDOMRange(nsIDOMRange *aRangeToCheck, nsISelection *aSelectionOfWords)
{
NS_ENSURE_ARG_POINTER(aRangeToCheck);
NS_ENSURE_ARG_POINTER(aSelectionOfWords);
NS_ENSURE_TRUE(mSpellChecker && mWordBreaker, NS_ERROR_NULL_POINTER);
NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
mDocument->InitWithRange(aRangeToCheck);
nsSpellCheckUtils::LoadTextBlockIntoBuffer(mDocument, mSpellChecker, mBlockBuffer, mText, mOffset);
// You may want to clear this externally instead of here
aSelectionOfWords->RemoveAllRanges();
// Now get the next misspelled word in the document.
const PRUnichar* text = mText.get();
PRUint32 textLen = mText.Length();
PRUint32 wlen = 0;
PRUint32 beginWord = 0;
PRUint32 endWord = 0;
PRBool isMisspelled = PR_TRUE;
while (isMisspelled) {
PRUnichar* word;
nsresult result = FindNextMisspelledWord(text, textLen, mOffset, wlen,
beginWord, endWord, word, PR_FALSE, isMisspelled);
NS_ENSURE_SUCCESS(result, result);
if (isMisspelled) {// We found an occurrence of old word, so replace it!
nsCOMPtr<nsIDOMRange> range;
result = mDocument->GetDOMRangeFor(beginWord, wlen, getter_AddRefs(range));
NS_ENSURE_SUCCESS(result, result);
result = aSelectionOfWords->AddRange(range);
NS_ENSURE_SUCCESS(result, result);
}
}
return NS_OK;
}
//-------------------------------------------------------------
// Helper Method
nsresult
nsSpellCheckController::FindNextMisspelledWord(const PRUnichar* aText,
const PRUint32& aTextLen,
PRUint32& aOffset,
PRUint32& aWLen,
PRUint32& aBeginWord,
PRUint32& aEndWord,
PRUnichar*& aWord,
PRBool aReturnWord,
PRBool& aIsMisspelled)
{
aWord = nsnull;
aIsMisspelled = PR_FALSE;
#ifdef DEBUG_rods
DUMPWORDS(mWordBreaker, aText, aTextLen);
#endif
while (aOffset < aTextLen) {
nsresult result = mWordBreaker->FindWord(aText, aTextLen, aOffset, &aBeginWord, &aEndWord);
NS_ENSURE_SUCCESS(result, result);
aWLen = aEndWord - aBeginWord;
// The wordBreaker hands back the spaces inbetween the words
// so we need to skip any words that are all spaces
const PRUnichar* start = (aText+aOffset);
const PRUnichar* endPtr = (aText+aEndWord);
while (*start == NBSP_SPACE_CODE && start < endPtr)
start++;
if (start == endPtr) {
aOffset = aEndWord;
continue;
}
PRUnichar* word = nsCRT::strndup(start, aWLen);
aOffset = aEndWord;
result = mSpellChecker->Check(word, &aIsMisspelled);
#if DEBUG_rods
printf("Word [%s] MSP: %s\n", NS_LossyConvertUCS2toASCII(word).get(), aIsMisspelled ? "YES":"NO");
#endif
if (NS_FAILED(result)) {
nsMemory::Free(word);
return result;
}
if (aIsMisspelled) {
if (aReturnWord) {
aWord = word;
} else {
nsMemory::Free(word);
}
return NS_OK;
}
nsMemory::Free(word);
}
return NS_OK;
}