2010-10-26 MORITA Hajime <morrita@google.com>
authormorrita@google.com <morrita@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 1 Nov 2010 06:35:59 +0000 (06:35 +0000)
committermorrita@google.com <morrita@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 1 Nov 2010 06:35:59 +0000 (06:35 +0000)
        Reviewed by Kent Tamura.

        Refactoring: Spellchecking related static functions could form a class
        https://bugs.webkit.org/show_bug.cgi?id=48287

        Extracted spellcheck related static functions to TextCheckingHelper class,
        which has EditorClient and Range as its member.

        No new tests. Just a refactoring.

        * CMakeLists.txt:
        * GNUmakefile.am:
        * WebCore.gypi:
        * WebCore.pro:
        * WebCore.vcproj/WebCore.vcproj:
        * WebCore.xcodeproj/project.pbxproj:
        * editing/EditingAllInOne.cpp
        * editing/Editor.cpp:
        (WebCore::Editor::advanceToNextMisspelling):
        (WebCore::Editor::isSelectionUngrammatical):
        (WebCore::Editor::guessesForUngrammaticalSelection):
        (WebCore::Editor::guessesForMisspelledOrUngrammaticalSelection):
        (WebCore::Editor::markMisspellingsAfterTypingToPosition):
        (WebCore::Editor::markMisspellingsOrBadGrammar):
        (WebCore::Editor::markMisspellings):
        (WebCore::Editor::markBadGrammar):
        (WebCore::Editor::markAllMisspellingsAndBadGrammarInRanges):
        (WebCore::Editor::changeBackToReplacedString):
        * editing/Editor.h:
        * editing/TextCheckingHelper.cpp: Added.
        (WebCore::TextCheckingHelper::TextCheckingHelper):
        (WebCore::TextCheckingHelper::~TextCheckingHelper):
        (WebCore::TextCheckingHelper::paragraphAlignedRange):
        (WebCore::TextCheckingHelper::findFirstMisspelling):
        (WebCore::TextCheckingHelper::findFirstMisspellingOrBadGrammar):
        (WebCore::TextCheckingHelper::findFirstGrammarDetail):
        (WebCore::TextCheckingHelper::findFirstBadGrammar):
        (WebCore::TextCheckingHelper::isUngrammatical):
        (WebCore::TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange):
        (WebCore::TextCheckingHelper::markAllMisspellings):
        (WebCore::TextCheckingHelper::markAllBadGrammar):
        * editing/TextCheckingHelper.h: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@71009 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
WebCore/CMakeLists.txt
WebCore/ChangeLog
WebCore/GNUmakefile.am
WebCore/WebCore.gypi
WebCore/WebCore.pro
WebCore/WebCore.vcproj/WebCore.vcproj
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/editing/EditingAllInOne.cpp
WebCore/editing/Editor.cpp
WebCore/editing/Editor.h
WebCore/editing/TextCheckingHelper.cpp [new file with mode: 0644]
WebCore/editing/TextCheckingHelper.h [new file with mode: 0644]

index c1a8e11..4b05477 100644 (file)
@@ -926,6 +926,7 @@ SET(WebCore_SOURCES
     editing/SplitElementCommand.cpp
     editing/SplitTextNodeCommand.cpp
     editing/SplitTextNodeContainingElementCommand.cpp
+    editing/TextCheckingHelper.cpp
     editing/TextIterator.cpp
     editing/TypingCommand.cpp
     editing/UnlinkCommand.cpp
index 90df85d..af76c10 100644 (file)
@@ -1,3 +1,48 @@
+2010-10-26  MORITA Hajime  <morrita@google.com>
+
+        Reviewed by Kent Tamura.
+
+        Refactoring: Spellchecking related static functions could form a class
+        https://bugs.webkit.org/show_bug.cgi?id=48287
+
+        Extracted spellcheck related static functions to TextCheckingHelper class,
+        which has EditorClient and Range as its member.
+        
+        No new tests. Just a refactoring.
+
+        * CMakeLists.txt:
+        * GNUmakefile.am:
+        * WebCore.gypi:
+        * WebCore.pro:
+        * WebCore.vcproj/WebCore.vcproj:
+        * WebCore.xcodeproj/project.pbxproj:
+        * editing/EditingAllInOne.cpp
+        * editing/Editor.cpp:
+        (WebCore::Editor::advanceToNextMisspelling):
+        (WebCore::Editor::isSelectionUngrammatical):
+        (WebCore::Editor::guessesForUngrammaticalSelection):
+        (WebCore::Editor::guessesForMisspelledOrUngrammaticalSelection):
+        (WebCore::Editor::markMisspellingsAfterTypingToPosition):
+        (WebCore::Editor::markMisspellingsOrBadGrammar):
+        (WebCore::Editor::markMisspellings):
+        (WebCore::Editor::markBadGrammar):
+        (WebCore::Editor::markAllMisspellingsAndBadGrammarInRanges):
+        (WebCore::Editor::changeBackToReplacedString):
+        * editing/Editor.h:
+        * editing/TextCheckingHelper.cpp: Added.
+        (WebCore::TextCheckingHelper::TextCheckingHelper):
+        (WebCore::TextCheckingHelper::~TextCheckingHelper):
+        (WebCore::TextCheckingHelper::paragraphAlignedRange):
+        (WebCore::TextCheckingHelper::findFirstMisspelling):
+        (WebCore::TextCheckingHelper::findFirstMisspellingOrBadGrammar):
+        (WebCore::TextCheckingHelper::findFirstGrammarDetail):
+        (WebCore::TextCheckingHelper::findFirstBadGrammar):
+        (WebCore::TextCheckingHelper::isUngrammatical):
+        (WebCore::TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange):
+        (WebCore::TextCheckingHelper::markAllMisspellings):
+        (WebCore::TextCheckingHelper::markAllBadGrammar):
+        * editing/TextCheckingHelper.h: Added.
+
 2010-10-31  Xan Lopez  <xlopez@igalia.com>
 
         Try to fix the GTK+ build.
index f721608..7fe1dac 100644 (file)
@@ -1355,6 +1355,8 @@ webcore_sources += \
        WebCore/editing/SplitTextNodeContainingElementCommand.cpp \
        WebCore/editing/SplitTextNodeContainingElementCommand.h \
        WebCore/editing/TextAffinity.h \
+       WebCore/editing/TextCheckingHelper.cpp \
+       WebCore/editing/TextCheckingHelper.h \
        WebCore/editing/TextGranularity.h \
        WebCore/editing/TextIterator.cpp \
        WebCore/editing/TextIterator.h \
index a0c3d52..d9c5e39 100644 (file)
             'editing/SplitTextNodeContainingElementCommand.cpp',
             'editing/SplitTextNodeContainingElementCommand.h',
             'editing/TextAffinity.h',
+            'editing/TextCheckingHelper.cpp',
+            'editing/TextCheckingHelper.h',
             'editing/TextGranularity.h',
             'editing/TextIterator.cpp',
             'editing/TextIterator.h',
index 34697df..4f3ecc0 100644 (file)
@@ -817,6 +817,7 @@ SOURCES += \
     editing/SplitElementCommand.cpp \
     editing/SplitTextNodeCommand.cpp \
     editing/SplitTextNodeContainingElementCommand.cpp \
+    editing/TextCheckingHelper.cpp \
     editing/TextIterator.cpp \
     editing/TypingCommand.cpp \
     editing/UnlinkCommand.cpp \
index 743228b..65056f7 100644 (file)
                                >\r
                        </File>\r
                        <File\r
+                               RelativePath="..\editing\TextCheckingHelper.cpp"\r
+                               >\r
+                               <FileConfiguration\r
+                                       Name="Debug|Win32"\r
+                                       ExcludedFromBuild="true"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Release|Win32"\r
+                                       ExcludedFromBuild="true"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Debug_Internal|Win32"\r
+                                       ExcludedFromBuild="true"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Debug_Cairo|Win32"\r
+                                       ExcludedFromBuild="true"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Release_Cairo|Win32"\r
+                                       ExcludedFromBuild="true"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Debug_All|Win32"\r
+                                       ExcludedFromBuild="true"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                       />\r
+                               </FileConfiguration>\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\editing\TextCheckingHelper.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
                                RelativePath="..\editing\TextGranularity.h"\r
                                >\r
                        </File>\r
index 15dfd62..c1bf23b 100644 (file)
                A7D3C5240B576B4B002CA450 /* PasteboardHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D3C5230B576B4B002CA450 /* PasteboardHelper.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A7D6B3490F61104500B79FD1 /* WorkerScriptLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D6B3470F61104500B79FD1 /* WorkerScriptLoader.h */; };
                A7D6B34A0F61104500B79FD1 /* WorkerScriptLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D6B3480F61104500B79FD1 /* WorkerScriptLoader.cpp */; };
+               A7DBF8DD1276919C006B6008 /* TextCheckingHelper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7DBF8DB1276919C006B6008 /* TextCheckingHelper.cpp */; };
+               A7DBF8DE1276919C006B6008 /* TextCheckingHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = A7DBF8DC1276919C006B6008 /* TextCheckingHelper.h */; };
                A7F338A311C0EFCA00A320A7 /* ShadowElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7F338A111C0EFCA00A320A7 /* ShadowElement.cpp */; };
                A7F338A411C0EFCA00A320A7 /* ShadowElement.h in Headers */ = {isa = PBXBuildFile; fileRef = A7F338A211C0EFCA00A320A7 /* ShadowElement.h */; };
                A809F1470B73793A002E4D7F /* RenderSVGGradientStop.h in Headers */ = {isa = PBXBuildFile; fileRef = A809F1450B73793A002E4D7F /* RenderSVGGradientStop.h */; };
                A7D3C5230B576B4B002CA450 /* PasteboardHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PasteboardHelper.h; sourceTree = "<group>"; };
                A7D6B3470F61104500B79FD1 /* WorkerScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WorkerScriptLoader.h; path = workers/WorkerScriptLoader.h; sourceTree = "<group>"; };
                A7D6B3480F61104500B79FD1 /* WorkerScriptLoader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WorkerScriptLoader.cpp; path = workers/WorkerScriptLoader.cpp; sourceTree = "<group>"; };
+               A7DBF8DB1276919C006B6008 /* TextCheckingHelper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextCheckingHelper.cpp; sourceTree = "<group>"; };
+               A7DBF8DC1276919C006B6008 /* TextCheckingHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextCheckingHelper.h; sourceTree = "<group>"; };
                A7F338A111C0EFCA00A320A7 /* ShadowElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ShadowElement.cpp; sourceTree = "<group>"; };
                A7F338A211C0EFCA00A320A7 /* ShadowElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShadowElement.h; sourceTree = "<group>"; };
                A809F1450B73793A002E4D7F /* RenderSVGGradientStop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderSVGGradientStop.h; sourceTree = "<group>"; };
                                93309DC7099E64910056E581 /* SplitTextNodeContainingElementCommand.h */,
                                93309DC8099E64910056E581 /* TextAffinity.h */,
                                93309DC9099E64910056E581 /* TextGranularity.h */,
+                               A7DBF8DB1276919C006B6008 /* TextCheckingHelper.cpp */,
+                               A7DBF8DC1276919C006B6008 /* TextCheckingHelper.h */,
                                93309DCC099E64910056E581 /* TextIterator.cpp */,
                                93309DCD099E64910056E581 /* TextIterator.h */,
                                93309DCA099E64910056E581 /* TypingCommand.cpp */,
                                B2C3DA340D006C1D00EF6F26 /* TextBoundaries.h in Headers */,
                                B2C3DA360D006C1D00EF6F26 /* TextBreakIterator.h in Headers */,
                                B2C3DA380D006C1D00EF6F26 /* TextBreakIteratorInternalICU.h in Headers */,
+                               A7DBF8DE1276919C006B6008 /* TextCheckingHelper.h in Headers */,
                                B2C3DA3A0D006C1D00EF6F26 /* TextCodec.h in Headers */,
                                B2C3DA3C0D006C1D00EF6F26 /* TextCodecICU.h in Headers */,
                                B2C3DA3E0D006C1D00EF6F26 /* TextCodecLatin1.h in Headers */,
                                B2AFFC970D00A5DF0030074D /* TextBoundaries.mm in Sources */,
                                B2C3DA370D006C1D00EF6F26 /* TextBreakIteratorICU.cpp in Sources */,
                                B2AFFC980D00A5DF0030074D /* TextBreakIteratorInternalICUMac.mm in Sources */,
+                               A7DBF8DD1276919C006B6008 /* TextCheckingHelper.cpp in Sources */,
                                B2C3DA390D006C1D00EF6F26 /* TextCodec.cpp in Sources */,
                                B2C3DA3B0D006C1D00EF6F26 /* TextCodecICU.cpp in Sources */,
                                B2C3DA3D0D006C1D00EF6F26 /* TextCodecLatin1.cpp in Sources */,
index ba484be..81483f8 100644 (file)
@@ -65,6 +65,7 @@
 #include <SplitElementCommand.cpp>
 #include <SplitTextNodeCommand.cpp>
 #include <SplitTextNodeContainingElementCommand.cpp>
+#include <TextCheckingHelper.cpp>
 #include <TextIterator.cpp>
 #include <TypingCommand.cpp>
 #include <UnlinkCommand.cpp>
index 169b765..8a4ef59 100644 (file)
@@ -62,6 +62,7 @@
 #include "NodeList.h"
 #include "Page.h"
 #include "Pasteboard.h"
+#include "TextCheckingHelper.h"
 #include "RemoveFormatCommand.h"
 #include "RenderBlock.h"
 #include "RenderPart.h"
@@ -1659,328 +1660,6 @@ void Editor::learnSpelling()
     client()->learnWord(text);
 }
 
-static String findFirstMisspellingInRange(EditorClient* client, Range* searchRange, int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
-{
-    ASSERT_ARG(client, client);
-    ASSERT_ARG(searchRange, searchRange);
-    
-    WordAwareIterator it(searchRange);
-    firstMisspellingOffset = 0;
-    
-    String firstMisspelling;
-    int currentChunkOffset = 0;
-
-    while (!it.atEnd()) {
-        const UChar* chars = it.characters();
-        int len = it.length();
-        
-        // Skip some work for one-space-char hunks
-        if (!(len == 1 && chars[0] == ' ')) {
-            
-            int misspellingLocation = -1;
-            int misspellingLength = 0;
-            client->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
-
-            // 5490627 shows that there was some code path here where the String constructor below crashes.
-            // We don't know exactly what combination of bad input caused this, so we're making this much
-            // more robust against bad input on release builds.
-            ASSERT(misspellingLength >= 0);
-            ASSERT(misspellingLocation >= -1);
-            ASSERT(!misspellingLength || misspellingLocation >= 0);
-            ASSERT(misspellingLocation < len);
-            ASSERT(misspellingLength <= len);
-            ASSERT(misspellingLocation + misspellingLength <= len);
-            
-            if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
-                
-                // Compute range of misspelled word
-                RefPtr<Range> misspellingRange = TextIterator::subrange(searchRange, currentChunkOffset + misspellingLocation, misspellingLength);
-
-                // Remember first-encountered misspelling and its offset.
-                if (!firstMisspelling) {
-                    firstMisspellingOffset = currentChunkOffset + misspellingLocation;
-                    firstMisspelling = String(chars + misspellingLocation, misspellingLength);
-                    firstMisspellingRange = misspellingRange;
-                }
-
-                // Store marker for misspelled word.
-                ExceptionCode ec = 0;
-                misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
-                ASSERT(!ec);
-
-                // Bail out if we're marking only the first misspelling, and not all instances.
-                if (!markAll)
-                    break;
-            }
-        }
-        
-        currentChunkOffset += len;
-        it.advance();
-    }
-    
-    return firstMisspelling;
-}
-
-#ifndef BUILDING_ON_TIGER
-
-static PassRefPtr<Range> paragraphAlignedRangeForRange(Range* arbitraryRange, int& offsetIntoParagraphAlignedRange, String& paragraphString)
-{
-    ASSERT_ARG(arbitraryRange, arbitraryRange);
-    
-    ExceptionCode ec = 0;
-    
-    // Expand range to paragraph boundaries
-    RefPtr<Range> paragraphRange = arbitraryRange->cloneRange(ec);
-    setStart(paragraphRange.get(), startOfParagraph(arbitraryRange->startPosition()));
-    setEnd(paragraphRange.get(), endOfParagraph(arbitraryRange->endPosition()));
-    
-    // Compute offset from start of expanded range to start of original range
-    RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), arbitraryRange->startPosition());
-    offsetIntoParagraphAlignedRange = TextIterator::rangeLength(offsetAsRange.get());
-    
-    // Fill in out parameter with string representing entire paragraph range.
-    // Someday we might have a caller that doesn't use this, but for now all callers do.
-    paragraphString = plainText(paragraphRange.get());
-
-    return paragraphRange;
-}
-
-static int findFirstGrammarDetailInRange(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, Range *searchRange, int startOffset, int endOffset, bool markAll)
-{
-    // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
-    // Optionally add a DocumentMarker for each detail in the range.
-    int earliestDetailLocationSoFar = -1;
-    int earliestDetailIndex = -1;
-    for (unsigned i = 0; i < grammarDetails.size(); i++) {
-        const GrammarDetail* detail = &grammarDetails[i];
-        ASSERT(detail->length > 0 && detail->location >= 0);
-        
-        int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
-        
-        // Skip this detail if it starts before the original search range
-        if (detailStartOffsetInParagraph < startOffset)
-            continue;
-        
-        // Skip this detail if it starts after the original search range
-        if (detailStartOffsetInParagraph >= endOffset)
-            continue;
-        
-        if (markAll) {
-            RefPtr<Range> badGrammarRange = TextIterator::subrange(searchRange, badGrammarPhraseLocation - startOffset + detail->location, detail->length);
-            ExceptionCode ec = 0;
-            badGrammarRange->startContainer(ec)->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
-            ASSERT(!ec);
-        }
-        
-        // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
-        if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
-            earliestDetailIndex = i;
-            earliestDetailLocationSoFar = detail->location;
-        }
-    }
-    
-    return earliestDetailIndex;
-}
-    
-static String findFirstBadGrammarInRange(EditorClient* client, Range* searchRange, GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
-{
-    ASSERT_ARG(client, client);
-    ASSERT_ARG(searchRange, searchRange);
-    
-    // Initialize out parameters; these will be updated if we find something to return.
-    outGrammarDetail.location = -1;
-    outGrammarDetail.length = 0;
-    outGrammarDetail.guesses.clear();
-    outGrammarDetail.userDescription = "";
-    outGrammarPhraseOffset = 0;
-    
-    String firstBadGrammarPhrase;
-
-    // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
-    // Determine the character offset from the start of the paragraph to the start of the original search range,
-    // since we will want to ignore results in this area.
-    int searchRangeStartOffset;
-    String paragraphString;
-    RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(searchRange, searchRangeStartOffset, paragraphString);
-        
-    // Determine the character offset from the start of the paragraph to the end of the original search range, 
-    // since we will want to ignore results in this area also.
-    int searchRangeEndOffset = searchRangeStartOffset + TextIterator::rangeLength(searchRange);
-        
-    // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
-    int startOffset = 0;
-    while (startOffset < searchRangeEndOffset) {
-        Vector<GrammarDetail> grammarDetails;
-        int badGrammarPhraseLocation = -1;
-        int badGrammarPhraseLength = 0;
-        client->checkGrammarOfString(paragraphString.characters() + startOffset, paragraphString.length() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
-        
-        if (!badGrammarPhraseLength) {
-            ASSERT(badGrammarPhraseLocation == -1);
-            return String();
-        }
-
-        ASSERT(badGrammarPhraseLocation >= 0);
-        badGrammarPhraseLocation += startOffset;
-
-        
-        // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
-        int badGrammarIndex = findFirstGrammarDetailInRange(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, searchRange, searchRangeStartOffset, searchRangeEndOffset, markAll);
-        if (badGrammarIndex >= 0) {
-            ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
-            outGrammarDetail = grammarDetails[badGrammarIndex];
-        }
-
-        // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
-        // kept going so we could mark all instances).
-        if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
-            outGrammarPhraseOffset = badGrammarPhraseLocation - searchRangeStartOffset;
-            firstBadGrammarPhrase = paragraphString.substring(badGrammarPhraseLocation, badGrammarPhraseLength);
-            
-            // Found one. We're done now, unless we're marking each instance.
-            if (!markAll)
-                break;
-        }
-
-        // These results were all between the start of the paragraph and the start of the search range; look
-        // beyond this phrase.
-        startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
-    }
-    
-    return firstBadGrammarPhrase;
-}
-    
-#endif /* not BUILDING_ON_TIGER */
-
-#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
-
-static String findFirstMisspellingOrBadGrammarInRange(EditorClient* client, Range* searchRange, bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
-{
-    ASSERT_ARG(client, client);
-    ASSERT_ARG(searchRange, searchRange);
-    
-    String firstFoundItem;
-    String misspelledWord;
-    String badGrammarPhrase;
-    ExceptionCode ec = 0;
-    
-    // Initialize out parameters; these will be updated if we find something to return.
-    outIsSpelling = true;
-    outFirstFoundOffset = 0;
-    outGrammarDetail.location = -1;
-    outGrammarDetail.length = 0;
-    outGrammarDetail.guesses.clear();
-    outGrammarDetail.userDescription = "";
-    
-    // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
-    // Determine the character offset from the start of the paragraph to the start of the original search range,
-    // since we will want to ignore results in this area.
-    RefPtr<Range> paragraphRange = searchRange->cloneRange(ec);
-    setStart(paragraphRange.get(), startOfParagraph(searchRange->startPosition()));
-    int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
-    setEnd(paragraphRange.get(), endOfParagraph(searchRange->startPosition()));
-    
-    RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), searchRange->startPosition());
-    int searchRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
-    int totalLengthProcessed = 0;
-    
-    bool firstIteration = true;
-    bool lastIteration = false;
-    while (totalLengthProcessed < totalRangeLength) {
-        // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
-        int currentLength = TextIterator::rangeLength(paragraphRange.get());
-        int currentStartOffset = firstIteration ? searchRangeStartOffset : 0;
-        int currentEndOffset = currentLength;
-        if (inSameParagraph(paragraphRange->startPosition(), searchRange->endPosition())) {
-            // Determine the character offset from the end of the original search range to the end of the paragraph,
-            // since we will want to ignore results in this area.
-            RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), searchRange->endPosition());
-            currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
-            lastIteration = true;
-        }
-        if (currentStartOffset < currentEndOffset) {
-            String paragraphString = plainText(paragraphRange.get());
-            if (paragraphString.length() > 0) {
-                bool foundGrammar = false;
-                int spellingLocation = 0;
-                int grammarPhraseLocation = 0;
-                int grammarDetailLocation = 0;
-                unsigned grammarDetailIndex = 0;
-                
-                Vector<TextCheckingResult> results;
-                uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
-                client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
-                
-                for (unsigned i = 0; i < results.size(); i++) {
-                    const TextCheckingResult* result = &results[i];
-                    if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
-                        ASSERT(result->length > 0 && result->location >= 0);
-                        spellingLocation = result->location;
-                        misspelledWord = paragraphString.substring(result->location, result->length);
-                        ASSERT(misspelledWord.length());
-                        break;
-                    }
-                    if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
-                        ASSERT(result->length > 0 && result->location >= 0);
-                        // We can't stop after the first grammar result, since there might still be a spelling result after
-                        // it begins but before the first detail in it, but we can stop if we find a second grammar result.
-                        if (foundGrammar)
-                            break;
-                        for (unsigned j = 0; j < result->details.size(); j++) {
-                            const GrammarDetail* detail = &result->details[j];
-                            ASSERT(detail->length > 0 && detail->location >= 0);
-                            if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
-                                grammarDetailIndex = j;
-                                grammarDetailLocation = result->location + detail->location;
-                                foundGrammar = true;
-                            }
-                        }
-                        if (foundGrammar) {
-                            grammarPhraseLocation = result->location;
-                            outGrammarDetail = result->details[grammarDetailIndex];
-                            badGrammarPhrase = paragraphString.substring(result->location, result->length);
-                            ASSERT(badGrammarPhrase.length());
-                        }
-                    }
-                }
-
-                if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
-                    int spellingOffset = spellingLocation - currentStartOffset;
-                    if (!firstIteration) {
-                        RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), searchRange->startPosition(), paragraphRange->startPosition());
-                        spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
-                    }
-                    outIsSpelling = true;
-                    outFirstFoundOffset = spellingOffset;
-                    firstFoundItem = misspelledWord;
-                    break;
-                }
-                if (checkGrammar && !badGrammarPhrase.isEmpty()) {
-                    int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
-                    if (!firstIteration) {
-                        RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), searchRange->startPosition(), paragraphRange->startPosition());
-                        grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
-                    }
-                    outIsSpelling = false;
-                    outFirstFoundOffset = grammarPhraseOffset;
-                    firstFoundItem = badGrammarPhrase;
-                    break;
-                }
-            }
-        }
-        if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
-            break;
-        VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
-        setStart(paragraphRange.get(), newParagraphStart);
-        setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
-        firstIteration = false;
-        totalLengthProcessed += currentLength;
-    }
-    return firstFoundItem;
-}
-
-#endif
-
 void Editor::advanceToNextMisspelling(bool startBeforeSelection)
 {
     ExceptionCode ec = 0;
@@ -1992,6 +1671,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
     // repeated "check spelling" commands work.
     VisibleSelection selection(frame()->selection()->selection());
     RefPtr<Range> spellingSearchRange(rangeOfContents(frame()->document()));
+
     bool startedWithSelection = false;
     if (selection.start().node()) {
         startedWithSelection = true;
@@ -2057,7 +1737,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
     bool isSpelling = true;
     int foundOffset = 0;
     GrammarDetail grammarDetail;
-    String foundItem = findFirstMisspellingOrBadGrammarInRange(client(), spellingSearchRange.get(), isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
+    String foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
     if (isSpelling) {
         misspelledWord = foundItem;
         misspellingOffset = foundOffset;
@@ -2067,7 +1747,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
     }
 #else
     RefPtr<Range> firstMisspellingRange;
-    String misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false, firstMisspellingRange);
+    String misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange);
     String badGrammarPhrase;
 
 #ifndef BUILDING_ON_TIGER
@@ -2084,7 +1764,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
     }
     
     if (isGrammarCheckingEnabled())
-        badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false);
+        badGrammarPhrase = TextCheckingHelper(client(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
 #endif
 #endif
     
@@ -2097,7 +1777,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
         
 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
         grammarSearchRange = spellingSearchRange->cloneRange(ec);
-        foundItem = findFirstMisspellingOrBadGrammarInRange(client(), spellingSearchRange.get(), isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
+        foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
         if (isSpelling) {
             misspelledWord = foundItem;
             misspellingOffset = foundOffset;
@@ -2106,7 +1786,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
             grammarPhraseOffset = foundOffset;
         }
 #else
-        misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false, firstMisspellingRange);
+        misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange);
 
 #ifndef BUILDING_ON_TIGER
         grammarSearchRange = spellingSearchRange->cloneRange(ec);
@@ -2116,8 +1796,9 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
             chars.advance(misspellingOffset);
             grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec);
         }
+
         if (isGrammarCheckingEnabled())
-            badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false);
+            badGrammarPhrase = TextCheckingHelper(client(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
 #endif
 #endif
     }
@@ -2181,60 +1862,13 @@ bool Editor::isSelectionMisspelled()
     return true;
 }
 
-#ifndef BUILDING_ON_TIGER
-static bool isRangeUngrammatical(EditorClient* client, Range *range, Vector<String>& guessesVector)
-{
-    if (!client)
-        return false;
-
-    ExceptionCode ec;
-    if (!range || range->collapsed(ec))
-        return false;
-    
-    // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
-    // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
-    // or overlapping the range; the ranges must exactly match.
-    guessesVector.clear();
-    int grammarPhraseOffset;
-    
-    GrammarDetail grammarDetail;
-    String badGrammarPhrase = findFirstBadGrammarInRange(client, range, grammarDetail, grammarPhraseOffset, false);    
-    
-    // No bad grammar in these parts at all.
-    if (badGrammarPhrase.isEmpty())
-        return false;
-    
-    // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
-    if (grammarPhraseOffset > 0)
-        return false;
-    
-    ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
-    
-    // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
-    if (grammarDetail.location + grammarPhraseOffset)
-        return false;
-    
-    // Bad grammar at start of range, but end of bad grammar is before or after end of range
-    if (grammarDetail.length != TextIterator::rangeLength(range))
-        return false;
-    
-    // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
-    // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
-    // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
-    // or a grammar error.
-    client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
-    
-    return true;
-}
-#endif
-
 bool Editor::isSelectionUngrammatical()
 {
 #ifdef BUILDING_ON_TIGER
     return false;
 #else
     Vector<String> ignoredGuesses;
-    return isRangeUngrammatical(client(), frame()->selection()->toNormalizedRange().get(), ignoredGuesses);
+    return TextCheckingHelper(client(), frame()->selection()->toNormalizedRange()).isUngrammatical(ignoredGuesses);
 #endif
 }
 
@@ -2244,8 +1878,8 @@ Vector<String> Editor::guessesForUngrammaticalSelection()
     return Vector<String>();
 #else
     Vector<String> guesses;
-    // Ignore the result of isRangeUngrammatical; we just want the guesses, whether or not there are any
-    isRangeUngrammatical(client(), frame()->selection()->toNormalizedRange().get(), guesses);
+    // Ignore the result of isUngrammatical; we just want the guesses, whether or not there are any
+    TextCheckingHelper(client(), frame()->selection()->toNormalizedRange()).isUngrammatical(guesses);
     return guesses;
 #endif
 }
@@ -2261,72 +1895,10 @@ Vector<String> Editor::guessesForMisspelledSelection()
     return guesses;
 }
 
-#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
-
-static Vector<String> guessesForMisspelledOrUngrammaticalRange(EditorClient* client, Range *range, bool checkGrammar, bool& misspelled, bool& ungrammatical)
-{
-    Vector<String> guesses;
-    ExceptionCode ec;
-    misspelled = false;
-    ungrammatical = false;
-    
-    if (!client || !range || range->collapsed(ec))
-        return guesses;
-
-    // Expand the range to encompass entire paragraphs, since text checking needs that much context.
-    int rangeStartOffset;
-    String paragraphString;
-    RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(range, rangeStartOffset, paragraphString);
-    int rangeLength = TextIterator::rangeLength(range);
-    if (!rangeLength || !paragraphString.length())
-        return guesses;
-
-    Vector<TextCheckingResult> results;
-    uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
-    client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
-    
-    for (unsigned i = 0; i < results.size(); i++) {
-        const TextCheckingResult* result = &results[i];
-        if (result->type == TextCheckingTypeSpelling && result->location == rangeStartOffset && result->length == rangeLength) {
-            String misspelledWord = paragraphString.substring(rangeStartOffset, rangeLength);
-            ASSERT(misspelledWord.length());
-            client->getGuessesForWord(misspelledWord, guesses);
-            client->updateSpellingUIWithMisspelledWord(misspelledWord);
-            misspelled = true;
-            return guesses;
-        }
-    }
-    
-    if (!checkGrammar)
-        return guesses;
-        
-    for (unsigned i = 0; i < results.size(); i++) {
-        const TextCheckingResult* result = &results[i];
-        if (result->type == TextCheckingTypeGrammar && result->location <= rangeStartOffset && result->location + result->length >= rangeStartOffset + rangeLength) {
-            for (unsigned j = 0; j < result->details.size(); j++) {
-                const GrammarDetail* detail = &result->details[j];
-                ASSERT(detail->length > 0 && detail->location >= 0);
-                if (result->location + detail->location == rangeStartOffset && detail->length == rangeLength) {
-                    String badGrammarPhrase = paragraphString.substring(result->location, result->length);
-                    ASSERT(badGrammarPhrase.length());
-                    for (unsigned k = 0; k < detail->guesses.size(); k++)
-                        guesses.append(detail->guesses[k]);
-                    client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
-                    ungrammatical = true;
-                    return guesses;
-                }
-            }
-        }
-    }
-    return guesses;
-}
-
-#endif
-
 Vector<String> Editor::guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical)
 {
 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
-    return guessesForMisspelledOrUngrammaticalRange(client(), frame()->selection()->toNormalizedRange().get(), isGrammarCheckingEnabled(), misspelled, ungrammatical);
+    return TextCheckingHelper(client(), frame()->selection()->toNormalizedRange()).guessesForMisspelledOrUngrammaticalRange(isGrammarCheckingEnabled(), misspelled, ungrammatical);
 #else
     misspelled = isSelectionMisspelled();
     if (misspelled) {
@@ -2503,34 +2075,15 @@ void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p)
     markBadGrammar(VisibleSelection(startOfSentence(p), endOfSentence(p)));
 #endif
 }
-
-static void markAllMisspellingsInRange(EditorClient* client, Range* searchRange, RefPtr<Range>& firstMisspellingRange)
-{
-    // Use the "markAll" feature of findFirstMisspellingInRange. Ignore the return value and the "out parameter";
-    // all we need to do is mark every instance.
-    int ignoredOffset;
-    findFirstMisspellingInRange(client, searchRange, ignoredOffset, true, firstMisspellingRange);
-}
-
-#ifndef BUILDING_ON_TIGER
-static void markAllBadGrammarInRange(EditorClient* client, Range* searchRange)
-{
-    // Use the "markAll" feature of findFirstBadGrammarInRange. Ignore the return value and "out parameters"; all we need to
-    // do is mark every instance.
-    GrammarDetail ignoredGrammarDetail;
-    int ignoredOffset;
-    findFirstBadGrammarInRange(client, searchRange, ignoredGrammarDetail, ignoredOffset, true);
-}
-#endif
     
-static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection& selection, bool checkSpelling, RefPtr<Range>& firstMisspellingRange)
+void Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, bool checkSpelling, RefPtr<Range>& firstMisspellingRange)
 {
     // This function is called with a selection already expanded to word boundaries.
     // Might be nice to assert that here.
     
     // This function is used only for as-you-type checking, so if that's off we do nothing. Note that
     // grammar checking can only be on if spell checking is also on.
-    if (!editor->isContinuousSpellCheckingEnabled())
+    if (!isContinuousSpellCheckingEnabled())
         return;
     
     RefPtr<Range> searchRange(selection.toNormalizedRange());
@@ -2542,21 +2095,22 @@ static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection&
     if (!editableNode || !editableNode->isContentEditable())
         return;
 
-    if (!editor->isSpellCheckingEnabledInFocusedNode())
+    if (!isSpellCheckingEnabledInFocusedNode())
         return;
 
     // Get the spell checker if it is available
-    if (!editor->client())
+    if (!client())
         return;
     
+    TextCheckingHelper checker(client(), searchRange);
     if (checkSpelling)
-        markAllMisspellingsInRange(editor->client(), searchRange.get(), firstMisspellingRange);
+        checker.markAllMisspellings(firstMisspellingRange);
     else {
 #ifdef BUILDING_ON_TIGER
         ASSERT_NOT_REACHED();
 #else
-        if (editor->isGrammarCheckingEnabled())
-            markAllBadGrammarInRange(editor->client(), searchRange.get());
+        if (isGrammarCheckingEnabled())
+            checker.markAllBadGrammar();
 #endif
     }    
 }
@@ -2576,14 +2130,14 @@ bool Editor::isSpellCheckingEnabledInFocusedNode() const
 
 void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& firstMisspellingRange)
 {
-    markMisspellingsOrBadGrammar(this, selection, true, firstMisspellingRange);
+    markMisspellingsOrBadGrammar(selection, true, firstMisspellingRange);
 }
     
 void Editor::markBadGrammar(const VisibleSelection& selection)
 {
 #ifndef BUILDING_ON_TIGER
     RefPtr<Range> firstMisspellingRange;
-    markMisspellingsOrBadGrammar(this, selection, false, firstMisspellingRange);
+    markMisspellingsOrBadGrammar(selection, false, firstMisspellingRange);
 #else
     UNUSED_PARAM(selection);
 #endif
@@ -2636,12 +2190,12 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
 
     if (shouldMarkGrammar) {
         // The spelling range should be contained in the paragraph-aligned extension of the grammar range.
-        paragraphRange = paragraphAlignedRangeForRange(grammarRange, grammarRangeStartOffset, paragraphString);
+        paragraphRange = TextCheckingHelper(client(), grammarRange).paragraphAlignedRange(grammarRangeStartOffset, paragraphString);
         RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), spellingRange->startPosition());
         spellingRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
         grammarRangeEndOffset = grammarRangeStartOffset + TextIterator::rangeLength(grammarRange);
     } else {
-        paragraphRange = paragraphAlignedRangeForRange(spellingRange, spellingRangeStartOffset, paragraphString);
+        paragraphRange = TextCheckingHelper(client(), spellingRange).paragraphAlignedRange(spellingRangeStartOffset, paragraphString);
     }
     spellingRangeEndOffset = spellingRangeStartOffset + TextIterator::rangeLength(spellingRange);
     paragraphLength = paragraphString.length();
@@ -2831,10 +2385,10 @@ void Editor::changeBackToReplacedString(const String& replacedString)
     RefPtr<Range> selection = selectedRange();
     if (!shouldInsertText(replacedString, selection.get(), EditorInsertActionPasted))
         return;
-        
+    
     String paragraphString;
     int selectionOffset;
-    RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(selection.get(), selectionOffset, paragraphString);
+    RefPtr<Range> paragraphRange = TextCheckingHelper(client(), selection).paragraphAlignedRange(selectionOffset, paragraphString);
     replaceSelectionWithText(replacedString, false, false);
     RefPtr<Range> changedRange = TextIterator::subrange(paragraphRange.get(), selectionOffset, replacedString.length());
     changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::Replacement, String());
index e942560..110e3f9 100644 (file)
@@ -404,6 +404,7 @@ private:
     void replaceSelectionWithText(const String&, bool selectReplacement, bool smartReplace);
     void writeSelectionToPasteboard(Pasteboard*);
     void revealSelectionAfterEditingOperation();
+    void markMisspellingsOrBadGrammar(const VisibleSelection&, bool checkSpelling, RefPtr<Range>& firstMisspellingRange);
 
     void selectComposition();
     void confirmComposition(const String&, bool preserveSelection);
diff --git a/WebCore/editing/TextCheckingHelper.cpp b/WebCore/editing/TextCheckingHelper.cpp
new file mode 100644 (file)
index 0000000..49674ff
--- /dev/null
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "config.h"
+#include "TextCheckingHelper.h"
+
+#include "Range.h"
+#include "TextIterator.h"
+#include "VisiblePosition.h"
+#include "visible_units.h"
+
+namespace WebCore {
+
+TextCheckingHelper::TextCheckingHelper(EditorClient* client, PassRefPtr<Range> range)
+    : m_client(client)
+    , m_range(range)
+{
+    ASSERT_ARG(m_client, m_client);
+    ASSERT_ARG(m_range, m_range);
+}
+
+TextCheckingHelper::~TextCheckingHelper()
+{
+}
+
+PassRefPtr<Range> TextCheckingHelper::paragraphAlignedRange(int& offsetIntoParagraphAlignedRange, String& paragraphString) const
+{
+#ifndef BUILDING_ON_TIGER
+    ExceptionCode ec = 0;
+    
+    // Expand range to paragraph boundaries
+    RefPtr<Range> paragraphRange = m_range->cloneRange(ec);
+    setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
+    setEnd(paragraphRange.get(), endOfParagraph(m_range->endPosition()));
+    
+    // Compute offset from start of expanded range to start of original range
+    RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->startPosition());
+    offsetIntoParagraphAlignedRange = TextIterator::rangeLength(offsetAsRange.get());
+    
+    // Fill in out parameter with string representing entire paragraph range.
+    // Someday we might have a caller that doesn't use this, but for now all callers do.
+    paragraphString = plainText(paragraphRange.get());
+
+    return paragraphRange;
+#else
+    ASSERT_NOT_REACHED();
+    UNUSED_PARAM(offsetIntoParagraphAlignedRange);
+    UNUSED_PARAM(paragraphString);
+    return PassRefPtr<Range>(0);
+#endif
+}
+
+String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
+{
+    WordAwareIterator it(m_range.get());
+    firstMisspellingOffset = 0;
+    
+    String firstMisspelling;
+    int currentChunkOffset = 0;
+
+    while (!it.atEnd()) {
+        const UChar* chars = it.characters();
+        int len = it.length();
+        
+        // Skip some work for one-space-char hunks
+        if (!(len == 1 && chars[0] == ' ')) {
+            
+            int misspellingLocation = -1;
+            int misspellingLength = 0;
+            m_client->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
+
+            // 5490627 shows that there was some code path here where the String constructor below crashes.
+            // We don't know exactly what combination of bad input caused this, so we're making this much
+            // more robust against bad input on release builds.
+            ASSERT(misspellingLength >= 0);
+            ASSERT(misspellingLocation >= -1);
+            ASSERT(!misspellingLength || misspellingLocation >= 0);
+            ASSERT(misspellingLocation < len);
+            ASSERT(misspellingLength <= len);
+            ASSERT(misspellingLocation + misspellingLength <= len);
+            
+            if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
+                
+                // Compute range of misspelled word
+                RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
+
+                // Remember first-encountered misspelling and its offset.
+                if (!firstMisspelling) {
+                    firstMisspellingOffset = currentChunkOffset + misspellingLocation;
+                    firstMisspelling = String(chars + misspellingLocation, misspellingLength);
+                    firstMisspellingRange = misspellingRange;
+                }
+
+                // Store marker for misspelled word.
+                ExceptionCode ec = 0;
+                misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
+                ASSERT(!ec);
+
+                // Bail out if we're marking only the first misspelling, and not all instances.
+                if (!markAll)
+                    break;
+            }
+        }
+        
+        currentChunkOffset += len;
+        it.advance();
+    }
+    
+    return firstMisspelling;
+}
+
+String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
+{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+    String firstFoundItem;
+    String misspelledWord;
+    String badGrammarPhrase;
+    ExceptionCode ec = 0;
+    
+    // Initialize out parameters; these will be updated if we find something to return.
+    outIsSpelling = true;
+    outFirstFoundOffset = 0;
+    outGrammarDetail.location = -1;
+    outGrammarDetail.length = 0;
+    outGrammarDetail.guesses.clear();
+    outGrammarDetail.userDescription = "";
+    
+    // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
+    // Determine the character offset from the start of the paragraph to the start of the original search range,
+    // since we will want to ignore results in this area.
+    RefPtr<Range> paragraphRange = m_range->cloneRange(ec);
+    setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
+    int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
+    setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition()));
+    
+    RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->startPosition());
+    int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
+    int totalLengthProcessed = 0;
+    
+    bool firstIteration = true;
+    bool lastIteration = false;
+    while (totalLengthProcessed < totalRangeLength) {
+        // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
+        int currentLength = TextIterator::rangeLength(paragraphRange.get());
+        int currentStartOffset = firstIteration ? rangeStartOffset : 0;
+        int currentEndOffset = currentLength;
+        if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) {
+            // Determine the character offset from the end of the original search range to the end of the paragraph,
+            // since we will want to ignore results in this area.
+            RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->endPosition());
+            currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
+            lastIteration = true;
+        }
+        if (currentStartOffset < currentEndOffset) {
+            String paragraphString = plainText(paragraphRange.get());
+            if (paragraphString.length() > 0) {
+                bool foundGrammar = false;
+                int spellingLocation = 0;
+                int grammarPhraseLocation = 0;
+                int grammarDetailLocation = 0;
+                unsigned grammarDetailIndex = 0;
+                
+                Vector<TextCheckingResult> results;
+                uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
+                m_client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
+                
+                for (unsigned i = 0; i < results.size(); i++) {
+                    const TextCheckingResult* result = &results[i];
+                    if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
+                        ASSERT(result->length > 0 && result->location >= 0);
+                        spellingLocation = result->location;
+                        misspelledWord = paragraphString.substring(result->location, result->length);
+                        ASSERT(misspelledWord.length());
+                        break;
+                    }
+                    if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
+                        ASSERT(result->length > 0 && result->location >= 0);
+                        // We can't stop after the first grammar result, since there might still be a spelling result after
+                        // it begins but before the first detail in it, but we can stop if we find a second grammar result.
+                        if (foundGrammar)
+                            break;
+                        for (unsigned j = 0; j < result->details.size(); j++) {
+                            const GrammarDetail* detail = &result->details[j];
+                            ASSERT(detail->length > 0 && detail->location >= 0);
+                            if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
+                                grammarDetailIndex = j;
+                                grammarDetailLocation = result->location + detail->location;
+                                foundGrammar = true;
+                            }
+                        }
+                        if (foundGrammar) {
+                            grammarPhraseLocation = result->location;
+                            outGrammarDetail = result->details[grammarDetailIndex];
+                            badGrammarPhrase = paragraphString.substring(result->location, result->length);
+                            ASSERT(badGrammarPhrase.length());
+                        }
+                    }
+                }
+
+                if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
+                    int spellingOffset = spellingLocation - currentStartOffset;
+                    if (!firstIteration) {
+                        RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), m_range->startPosition(), paragraphRange->startPosition());
+                        spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
+                    }
+                    outIsSpelling = true;
+                    outFirstFoundOffset = spellingOffset;
+                    firstFoundItem = misspelledWord;
+                    break;
+                }
+                if (checkGrammar && !badGrammarPhrase.isEmpty()) {
+                    int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
+                    if (!firstIteration) {
+                        RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), m_range->startPosition(), paragraphRange->startPosition());
+                        grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
+                    }
+                    outIsSpelling = false;
+                    outFirstFoundOffset = grammarPhraseOffset;
+                    firstFoundItem = badGrammarPhrase;
+                    break;
+                }
+            }
+        }
+        if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
+            break;
+        VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
+        setStart(paragraphRange.get(), newParagraphStart);
+        setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
+        firstIteration = false;
+        totalLengthProcessed += currentLength;
+    }
+    return firstFoundItem;
+#else
+    ASSERT_NOT_REACHED();
+    UNUSED_PARAM(checkGrammar);
+    UNUSED_PARAM(outIsSpelling);
+    UNUSED_PARAM(outFirstFoundOffset);
+    UNUSED_PARAM(outGrammarDetail);
+    return "";
+#endif
+}
+
+int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, int startOffset, int endOffset, bool markAll)
+{
+#ifndef BUILDING_ON_TIGER
+    // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
+    // Optionally add a DocumentMarker for each detail in the range.
+    int earliestDetailLocationSoFar = -1;
+    int earliestDetailIndex = -1;
+    for (unsigned i = 0; i < grammarDetails.size(); i++) {
+        const GrammarDetail* detail = &grammarDetails[i];
+        ASSERT(detail->length > 0 && detail->location >= 0);
+        
+        int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
+        
+        // Skip this detail if it starts before the original search range
+        if (detailStartOffsetInParagraph < startOffset)
+            continue;
+        
+        // Skip this detail if it starts after the original search range
+        if (detailStartOffsetInParagraph >= endOffset)
+            continue;
+        
+        if (markAll) {
+            RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
+            ExceptionCode ec = 0;
+            badGrammarRange->startContainer(ec)->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
+            ASSERT(!ec);
+        }
+        
+        // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
+        if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
+            earliestDetailIndex = i;
+            earliestDetailLocationSoFar = detail->location;
+        }
+    }
+    
+    return earliestDetailIndex;
+#else
+    ASSERT_NOT_REACHED();
+    UNUSED_PARAM(grammarDetails);
+    UNUSED_PARAM(badGrammarPhraseLocation);
+    UNUSED_PARAM(startOffset);
+    UNUSED_PARAM(endOffset);
+    UNUSED_PARAM(markAll);
+    return 0;
+#endif
+}
+
+String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
+{
+#ifndef BUILDING_ON_TIGER
+    // Initialize out parameters; these will be updated if we find something to return.
+    outGrammarDetail.location = -1;
+    outGrammarDetail.length = 0;
+    outGrammarDetail.guesses.clear();
+    outGrammarDetail.userDescription = "";
+    outGrammarPhraseOffset = 0;
+    
+    String firstBadGrammarPhrase;
+
+    // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
+    // Determine the character offset from the start of the paragraph to the start of the original search range,
+    // since we will want to ignore results in this area.
+    int searchRangeStartOffset;
+    String paragraphString;
+    RefPtr<Range> paragraphRange = paragraphAlignedRange(searchRangeStartOffset, paragraphString);
+        
+    // Determine the character offset from the start of the paragraph to the end of the original search range, 
+    // since we will want to ignore results in this area also.
+    int searchRangeEndOffset = searchRangeStartOffset + TextIterator::rangeLength(m_range.get());
+        
+    // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
+    int startOffset = 0;
+    while (startOffset < searchRangeEndOffset) {
+        Vector<GrammarDetail> grammarDetails;
+        int badGrammarPhraseLocation = -1;
+        int badGrammarPhraseLength = 0;
+        m_client->checkGrammarOfString(paragraphString.characters() + startOffset, paragraphString.length() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
+        
+        if (!badGrammarPhraseLength) {
+            ASSERT(badGrammarPhraseLocation == -1);
+            return String();
+        }
+
+        ASSERT(badGrammarPhraseLocation >= 0);
+        badGrammarPhraseLocation += startOffset;
+
+        
+        // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
+        int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, searchRangeStartOffset, searchRangeEndOffset, markAll);
+        if (badGrammarIndex >= 0) {
+            ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
+            outGrammarDetail = grammarDetails[badGrammarIndex];
+        }
+
+        // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
+        // kept going so we could mark all instances).
+        if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
+            outGrammarPhraseOffset = badGrammarPhraseLocation - searchRangeStartOffset;
+            firstBadGrammarPhrase = paragraphString.substring(badGrammarPhraseLocation, badGrammarPhraseLength);
+            
+            // Found one. We're done now, unless we're marking each instance.
+            if (!markAll)
+                break;
+        }
+
+        // These results were all between the start of the paragraph and the start of the search range; look
+        // beyond this phrase.
+        startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
+    }
+    
+    return firstBadGrammarPhrase;
+#else
+    ASSERT_NOT_REACHED();
+    UNUSED_PARAM(outGrammarDetail);
+    UNUSED_PARAM(outGrammarPhraseOffset);
+    UNUSED_PARAM(markAll);
+#endif
+}
+
+
+bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const
+{
+#ifndef BUILDING_ON_TIGER
+    if (!m_client)
+        return false;
+
+    ExceptionCode ec;
+    if (!m_range || m_range->collapsed(ec))
+        return false;
+    
+    // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
+    // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
+    // or overlapping the range; the ranges must exactly match.
+    guessesVector.clear();
+    int grammarPhraseOffset;
+    
+    GrammarDetail grammarDetail;
+    String badGrammarPhrase = const_cast<TextCheckingHelper*>(this)->findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);    
+    
+    // No bad grammar in these parts at all.
+    if (badGrammarPhrase.isEmpty())
+        return false;
+    
+    // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
+    if (grammarPhraseOffset > 0)
+        return false;
+    
+    ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
+    
+    // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
+    if (grammarDetail.location + grammarPhraseOffset)
+        return false;
+    
+    // Bad grammar at start of range, but end of bad grammar is before or after end of range
+    if (grammarDetail.length != TextIterator::rangeLength(m_range.get()))
+        return false;
+    
+    // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
+    // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
+    // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
+    // or a grammar error.
+    m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
+    
+    return true;
+#else
+    ASSERT_NOT_REACHED();
+    UNUSED_PARAM(guessesVector);
+    return true;
+#endif
+}
+
+Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const
+{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+    Vector<String> guesses;
+    ExceptionCode ec;
+    misspelled = false;
+    ungrammatical = false;
+    
+    if (!m_client || !m_range || m_range->collapsed(ec))
+        return guesses;
+
+    // Expand the range to encompass entire paragraphs, since text checking needs that much context.
+    int rangeStartOffset;
+    String paragraphString;
+    RefPtr<Range> paragraphRange = paragraphAlignedRange(rangeStartOffset, paragraphString);
+    int rangeLength = TextIterator::rangeLength(m_range.get());
+    if (!rangeLength || !paragraphString.length())
+        return guesses;
+
+    Vector<TextCheckingResult> results;
+    uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
+    m_client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
+    
+    for (unsigned i = 0; i < results.size(); i++) {
+        const TextCheckingResult* result = &results[i];
+        if (result->type == TextCheckingTypeSpelling && result->location == rangeStartOffset && result->length == rangeLength) {
+            String misspelledWord = paragraphString.substring(rangeStartOffset, rangeLength);
+            ASSERT(misspelledWord.length());
+            m_client->getGuessesForWord(misspelledWord, guesses);
+            m_client->updateSpellingUIWithMisspelledWord(misspelledWord);
+            misspelled = true;
+            return guesses;
+        }
+    }
+    
+    if (!checkGrammar)
+        return guesses;
+        
+    for (unsigned i = 0; i < results.size(); i++) {
+        const TextCheckingResult* result = &results[i];
+        if (result->type == TextCheckingTypeGrammar && result->location <= rangeStartOffset && result->location + result->length >= rangeStartOffset + rangeLength) {
+            for (unsigned j = 0; j < result->details.size(); j++) {
+                const GrammarDetail* detail = &result->details[j];
+                ASSERT(detail->length > 0 && detail->location >= 0);
+                if (result->location + detail->location == rangeStartOffset && detail->length == rangeLength) {
+                    String badGrammarPhrase = paragraphString.substring(result->location, result->length);
+                    ASSERT(badGrammarPhrase.length());
+                    for (unsigned k = 0; k < detail->guesses.size(); k++)
+                        guesses.append(detail->guesses[k]);
+                    m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
+                    ungrammatical = true;
+                    return guesses;
+                }
+            }
+        }
+    }
+    return guesses;
+#else
+    ASSERT_NOT_REACHED();
+    UNUSED_PARAM(checkGrammar);
+    UNUSED_PARAM(misspelled);
+    UNUSED_PARAM(ungrammatical);
+    return Vector<String>();
+#endif
+}
+
+
+void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
+{
+    // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
+    // all we need to do is mark every instance.
+    int ignoredOffset;
+    findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
+}
+
+void TextCheckingHelper::markAllBadGrammar()
+{
+#ifndef BUILDING_ON_TIGER
+    // Use the "markAll" feature of findFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
+    // do is mark every instance.
+    GrammarDetail ignoredGrammarDetail;
+    int ignoredOffset;
+    findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
+#else
+    ASSERT_NOT_REACHED();
+#endif
+}
+
+}
diff --git a/WebCore/editing/TextCheckingHelper.h b/WebCore/editing/TextCheckingHelper.h
new file mode 100644 (file)
index 0000000..1dd3b3d
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef TextCheckingHelper_h
+#define TextCheckingHelper_h
+
+#include "EditorClient.h"
+
+namespace WebCore {
+
+class Range;
+
+class TextCheckingHelper : public Noncopyable {
+public:
+    TextCheckingHelper(EditorClient*, PassRefPtr<Range>);
+    ~TextCheckingHelper();
+
+    String findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange);
+    String findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail);
+    String findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll);
+    int findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int badGrammarPhraseLength, int startOffset, int endOffset, bool markAll);
+    void markAllMisspellings(RefPtr<Range>& firstMisspellingRange);
+    void markAllBadGrammar();
+
+    bool isUngrammatical(Vector<String>& guessesVector) const;
+    Vector<String> guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const;
+    PassRefPtr<Range> paragraphAlignedRange(int& offsetIntoParagraphAlignedRange, String& paragraphString) const;
+private:
+    EditorClient* m_client;
+    RefPtr<Range> m_range;
+};
+
+} // namespace WebCore
+
+#endif // TextCheckingHelper_h