OSDN Git Service

Merge WebKit at r73109: Initial merge by git.
[android-x86/external-webkit.git] / WebCore / editing / TextCheckingHelper.cpp
1 /*
2  * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "TextCheckingHelper.h"
29
30 #include "Range.h"
31 #include "TextIterator.h"
32 #include "VisiblePosition.h"
33 #include "visible_units.h"
34
35 namespace WebCore {
36
37 static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range)
38 {
39     ExceptionCode ec = 0;
40     RefPtr<Range> paragraphRange = range->cloneRange(ec);
41     setStart(paragraphRange.get(), startOfParagraph(range->startPosition()));
42     setEnd(paragraphRange.get(), endOfParagraph(range->endPosition()));
43     return paragraphRange;
44 }
45
46 TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange)
47     : m_checkingRange(checkingRange)
48     , m_checkingStart(-1)
49     , m_checkingEnd(-1)
50     , m_checkingLength(-1)
51 {
52 }
53
54 TextCheckingParagraph::~TextCheckingParagraph()
55 {
56 }
57
58 void TextCheckingParagraph::expandRangeToNextEnd()
59 {
60     ASSERT(m_checkingRange);
61     setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(paragraphRange()->startPosition())));
62     invalidateParagraphRangeValues();
63 }
64
65 void TextCheckingParagraph::invalidateParagraphRangeValues()
66 {
67     m_checkingStart = m_checkingEnd = -1;
68     m_offsetAsRange = 0;
69     m_text = String();
70 }
71
72 int TextCheckingParagraph::rangeLength() const
73 {
74     ASSERT(m_checkingRange);
75     return TextIterator::rangeLength(paragraphRange().get());
76 }
77
78 PassRefPtr<Range> TextCheckingParagraph::paragraphRange() const
79 {
80     ASSERT(m_checkingRange);
81     if (!m_paragraphRange)
82         m_paragraphRange = expandToParagraphBoundary(checkingRange());
83     return m_paragraphRange;
84 }
85
86 PassRefPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
87 {
88     ASSERT(m_checkingRange);
89     return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
90 }
91
92 int TextCheckingParagraph::offsetTo(const Position& position, ExceptionCode& ec) const
93 {
94     ASSERT(m_checkingRange);
95     RefPtr<Range> range = offsetAsRange();
96     range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), ec);
97     if (ec)
98         return 0;
99     return TextIterator::rangeLength(range.get());
100 }
101
102 bool TextCheckingParagraph::isEmpty() const
103 {
104     // Both predicates should have same result, but we check both just for sure.
105     // We need to investigate to remove this redundancy.
106     return isRangeEmpty() || isTextEmpty();
107 }
108
109 PassRefPtr<Range> TextCheckingParagraph::offsetAsRange() const
110 {
111     ASSERT(m_checkingRange);
112     if (!m_offsetAsRange) {
113         ExceptionCode ec = 0;
114         m_offsetAsRange = Range::create(paragraphRange()->startContainer(ec)->document(), paragraphRange()->startPosition(), checkingRange()->startPosition());
115     }
116
117     return m_offsetAsRange;
118 }
119
120 const String& TextCheckingParagraph::text() const
121 {
122     ASSERT(m_checkingRange);
123     if (m_text.isEmpty())
124         m_text = plainText(paragraphRange().get());
125     return m_text; 
126 }
127
128 int TextCheckingParagraph::checkingStart() const
129 {
130     ASSERT(m_checkingRange);
131     if (m_checkingStart == -1)
132         m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
133     return m_checkingStart;
134 }
135
136 int TextCheckingParagraph::checkingEnd() const
137 {
138     ASSERT(m_checkingRange);
139     if (m_checkingEnd == -1)
140         m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
141     return m_checkingEnd;
142 }
143
144 int TextCheckingParagraph::checkingLength() const
145 {
146     ASSERT(m_checkingRange);
147     if (-1 == m_checkingLength)
148         m_checkingLength = TextIterator::rangeLength(checkingRange().get());
149     return m_checkingLength;
150 }
151
152 TextCheckingHelper::TextCheckingHelper(EditorClient* client, PassRefPtr<Range> range)
153     : m_client(client)
154     , m_range(range)
155 {
156     ASSERT_ARG(m_client, m_client);
157     ASSERT_ARG(m_range, m_range);
158 }
159
160 TextCheckingHelper::~TextCheckingHelper()
161 {
162 }
163
164 String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
165 {
166     WordAwareIterator it(m_range.get());
167     firstMisspellingOffset = 0;
168     
169     String firstMisspelling;
170     int currentChunkOffset = 0;
171
172     while (!it.atEnd()) {
173         const UChar* chars = it.characters();
174         int len = it.length();
175         
176         // Skip some work for one-space-char hunks
177         if (!(len == 1 && chars[0] == ' ')) {
178             
179             int misspellingLocation = -1;
180             int misspellingLength = 0;
181             m_client->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
182
183             // 5490627 shows that there was some code path here where the String constructor below crashes.
184             // We don't know exactly what combination of bad input caused this, so we're making this much
185             // more robust against bad input on release builds.
186             ASSERT(misspellingLength >= 0);
187             ASSERT(misspellingLocation >= -1);
188             ASSERT(!misspellingLength || misspellingLocation >= 0);
189             ASSERT(misspellingLocation < len);
190             ASSERT(misspellingLength <= len);
191             ASSERT(misspellingLocation + misspellingLength <= len);
192             
193             if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
194                 
195                 // Compute range of misspelled word
196                 RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
197
198                 // Remember first-encountered misspelling and its offset.
199                 if (!firstMisspelling) {
200                     firstMisspellingOffset = currentChunkOffset + misspellingLocation;
201                     firstMisspelling = String(chars + misspellingLocation, misspellingLength);
202                     firstMisspellingRange = misspellingRange;
203                 }
204
205                 // Store marker for misspelled word.
206                 ExceptionCode ec = 0;
207                 misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
208                 ASSERT(!ec);
209
210                 // Bail out if we're marking only the first misspelling, and not all instances.
211                 if (!markAll)
212                     break;
213             }
214         }
215         
216         currentChunkOffset += len;
217         it.advance();
218     }
219     
220     return firstMisspelling;
221 }
222
223 String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
224 {
225 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
226     String firstFoundItem;
227     String misspelledWord;
228     String badGrammarPhrase;
229     ExceptionCode ec = 0;
230     
231     // Initialize out parameters; these will be updated if we find something to return.
232     outIsSpelling = true;
233     outFirstFoundOffset = 0;
234     outGrammarDetail.location = -1;
235     outGrammarDetail.length = 0;
236     outGrammarDetail.guesses.clear();
237     outGrammarDetail.userDescription = "";
238     
239     // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
240     // Determine the character offset from the start of the paragraph to the start of the original search range,
241     // since we will want to ignore results in this area.
242     RefPtr<Range> paragraphRange = m_range->cloneRange(ec);
243     setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
244     int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
245     setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition()));
246     
247     RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->startPosition());
248     int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
249     int totalLengthProcessed = 0;
250     
251     bool firstIteration = true;
252     bool lastIteration = false;
253     while (totalLengthProcessed < totalRangeLength) {
254         // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
255         int currentLength = TextIterator::rangeLength(paragraphRange.get());
256         int currentStartOffset = firstIteration ? rangeStartOffset : 0;
257         int currentEndOffset = currentLength;
258         if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) {
259             // Determine the character offset from the end of the original search range to the end of the paragraph,
260             // since we will want to ignore results in this area.
261             RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), m_range->endPosition());
262             currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
263             lastIteration = true;
264         }
265         if (currentStartOffset < currentEndOffset) {
266             String paragraphString = plainText(paragraphRange.get());
267             if (paragraphString.length() > 0) {
268                 bool foundGrammar = false;
269                 int spellingLocation = 0;
270                 int grammarPhraseLocation = 0;
271                 int grammarDetailLocation = 0;
272                 unsigned grammarDetailIndex = 0;
273                 
274                 Vector<TextCheckingResult> results;
275                 uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
276                 m_client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
277                 
278                 for (unsigned i = 0; i < results.size(); i++) {
279                     const TextCheckingResult* result = &results[i];
280                     if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
281                         ASSERT(result->length > 0 && result->location >= 0);
282                         spellingLocation = result->location;
283                         misspelledWord = paragraphString.substring(result->location, result->length);
284                         ASSERT(misspelledWord.length());
285                         break;
286                     }
287                     if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
288                         ASSERT(result->length > 0 && result->location >= 0);
289                         // We can't stop after the first grammar result, since there might still be a spelling result after
290                         // it begins but before the first detail in it, but we can stop if we find a second grammar result.
291                         if (foundGrammar)
292                             break;
293                         for (unsigned j = 0; j < result->details.size(); j++) {
294                             const GrammarDetail* detail = &result->details[j];
295                             ASSERT(detail->length > 0 && detail->location >= 0);
296                             if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
297                                 grammarDetailIndex = j;
298                                 grammarDetailLocation = result->location + detail->location;
299                                 foundGrammar = true;
300                             }
301                         }
302                         if (foundGrammar) {
303                             grammarPhraseLocation = result->location;
304                             outGrammarDetail = result->details[grammarDetailIndex];
305                             badGrammarPhrase = paragraphString.substring(result->location, result->length);
306                             ASSERT(badGrammarPhrase.length());
307                         }
308                     }
309                 }
310
311                 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
312                     int spellingOffset = spellingLocation - currentStartOffset;
313                     if (!firstIteration) {
314                         RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), m_range->startPosition(), paragraphRange->startPosition());
315                         spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
316                     }
317                     outIsSpelling = true;
318                     outFirstFoundOffset = spellingOffset;
319                     firstFoundItem = misspelledWord;
320                     break;
321                 }
322                 if (checkGrammar && !badGrammarPhrase.isEmpty()) {
323                     int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
324                     if (!firstIteration) {
325                         RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), m_range->startPosition(), paragraphRange->startPosition());
326                         grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
327                     }
328                     outIsSpelling = false;
329                     outFirstFoundOffset = grammarPhraseOffset;
330                     firstFoundItem = badGrammarPhrase;
331                     break;
332                 }
333             }
334         }
335         if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
336             break;
337         VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
338         setStart(paragraphRange.get(), newParagraphStart);
339         setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
340         firstIteration = false;
341         totalLengthProcessed += currentLength;
342     }
343     return firstFoundItem;
344 #else
345     ASSERT_NOT_REACHED();
346     UNUSED_PARAM(checkGrammar);
347     UNUSED_PARAM(outIsSpelling);
348     UNUSED_PARAM(outFirstFoundOffset);
349     UNUSED_PARAM(outGrammarDetail);
350     return "";
351 #endif
352 }
353
354 int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, int startOffset, int endOffset, bool markAll)
355 {
356 #ifndef BUILDING_ON_TIGER
357     // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
358     // Optionally add a DocumentMarker for each detail in the range.
359     int earliestDetailLocationSoFar = -1;
360     int earliestDetailIndex = -1;
361     for (unsigned i = 0; i < grammarDetails.size(); i++) {
362         const GrammarDetail* detail = &grammarDetails[i];
363         ASSERT(detail->length > 0 && detail->location >= 0);
364         
365         int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
366         
367         // Skip this detail if it starts before the original search range
368         if (detailStartOffsetInParagraph < startOffset)
369             continue;
370         
371         // Skip this detail if it starts after the original search range
372         if (detailStartOffsetInParagraph >= endOffset)
373             continue;
374         
375         if (markAll) {
376             RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
377             ExceptionCode ec = 0;
378             badGrammarRange->startContainer(ec)->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
379             ASSERT(!ec);
380         }
381         
382         // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
383         if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
384             earliestDetailIndex = i;
385             earliestDetailLocationSoFar = detail->location;
386         }
387     }
388     
389     return earliestDetailIndex;
390 #else
391     ASSERT_NOT_REACHED();
392     UNUSED_PARAM(grammarDetails);
393     UNUSED_PARAM(badGrammarPhraseLocation);
394     UNUSED_PARAM(startOffset);
395     UNUSED_PARAM(endOffset);
396     UNUSED_PARAM(markAll);
397     return 0;
398 #endif
399 }
400
401 String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
402 {
403 #ifndef BUILDING_ON_TIGER
404     // Initialize out parameters; these will be updated if we find something to return.
405     outGrammarDetail.location = -1;
406     outGrammarDetail.length = 0;
407     outGrammarDetail.guesses.clear();
408     outGrammarDetail.userDescription = "";
409     outGrammarPhraseOffset = 0;
410     
411     String firstBadGrammarPhrase;
412
413     // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
414     // Determine the character offset from the start of the paragraph to the start of the original search range,
415     // since we will want to ignore results in this area.
416     TextCheckingParagraph paragraph(m_range);
417     
418     // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
419     int startOffset = 0;
420     while (startOffset < paragraph.checkingEnd()) {
421         Vector<GrammarDetail> grammarDetails;
422         int badGrammarPhraseLocation = -1;
423         int badGrammarPhraseLength = 0;
424         m_client->checkGrammarOfString(paragraph.textCharacters() + startOffset, paragraph.textLength() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
425         
426         if (!badGrammarPhraseLength) {
427             ASSERT(badGrammarPhraseLocation == -1);
428             return String();
429         }
430
431         ASSERT(badGrammarPhraseLocation >= 0);
432         badGrammarPhraseLocation += startOffset;
433
434         
435         // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
436         int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
437         if (badGrammarIndex >= 0) {
438             ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
439             outGrammarDetail = grammarDetails[badGrammarIndex];
440         }
441
442         // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
443         // kept going so we could mark all instances).
444         if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
445             outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
446             firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
447             
448             // Found one. We're done now, unless we're marking each instance.
449             if (!markAll)
450                 break;
451         }
452
453         // These results were all between the start of the paragraph and the start of the search range; look
454         // beyond this phrase.
455         startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
456     }
457     
458     return firstBadGrammarPhrase;
459 #else
460     ASSERT_NOT_REACHED();
461     UNUSED_PARAM(outGrammarDetail);
462     UNUSED_PARAM(outGrammarPhraseOffset);
463     UNUSED_PARAM(markAll);
464 #endif
465 }
466
467
468 bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const
469 {
470 #ifndef BUILDING_ON_TIGER
471     if (!m_client)
472         return false;
473
474     ExceptionCode ec;
475     if (!m_range || m_range->collapsed(ec))
476         return false;
477     
478     // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
479     // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
480     // or overlapping the range; the ranges must exactly match.
481     guessesVector.clear();
482     int grammarPhraseOffset;
483     
484     GrammarDetail grammarDetail;
485     String badGrammarPhrase = const_cast<TextCheckingHelper*>(this)->findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);    
486     
487     // No bad grammar in these parts at all.
488     if (badGrammarPhrase.isEmpty())
489         return false;
490     
491     // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
492     if (grammarPhraseOffset > 0)
493         return false;
494     
495     ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
496     
497     // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
498     if (grammarDetail.location + grammarPhraseOffset)
499         return false;
500     
501     // Bad grammar at start of range, but end of bad grammar is before or after end of range
502     if (grammarDetail.length != TextIterator::rangeLength(m_range.get()))
503         return false;
504     
505     // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
506     // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
507     // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
508     // or a grammar error.
509     m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
510     
511     return true;
512 #else
513     ASSERT_NOT_REACHED();
514     UNUSED_PARAM(guessesVector);
515     return true;
516 #endif
517 }
518
519 Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const
520 {
521 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
522     Vector<String> guesses;
523     ExceptionCode ec;
524     misspelled = false;
525     ungrammatical = false;
526     
527     if (!m_client || !m_range || m_range->collapsed(ec))
528         return guesses;
529
530     // Expand the range to encompass entire paragraphs, since text checking needs that much context.
531     TextCheckingParagraph paragraph(m_range);
532     if (paragraph.isEmpty())
533         return guesses;
534
535     Vector<TextCheckingResult> results;
536     uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
537     m_client->checkTextOfParagraph(paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results);
538     
539     for (unsigned i = 0; i < results.size(); i++) {
540         const TextCheckingResult* result = &results[i];
541         if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) {
542             String misspelledWord = paragraph.checkingSubstring();
543             ASSERT(misspelledWord.length());
544             m_client->getGuessesForWord(misspelledWord, String(), guesses);
545             m_client->updateSpellingUIWithMisspelledWord(misspelledWord);
546             misspelled = true;
547             return guesses;
548         }
549     }
550     
551     if (!checkGrammar)
552         return guesses;
553         
554     for (unsigned i = 0; i < results.size(); i++) {
555         const TextCheckingResult* result = &results[i];
556         if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) {
557             for (unsigned j = 0; j < result->details.size(); j++) {
558                 const GrammarDetail* detail = &result->details[j];
559                 ASSERT(detail->length > 0 && detail->location >= 0);
560                 if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) {
561                     String badGrammarPhrase = paragraph.textSubstring(result->location, result->length);
562                     ASSERT(badGrammarPhrase.length());
563                     for (unsigned k = 0; k < detail->guesses.size(); k++)
564                         guesses.append(detail->guesses[k]);
565                     m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
566                     ungrammatical = true;
567                     return guesses;
568                 }
569             }
570         }
571     }
572     return guesses;
573 #else
574     ASSERT_NOT_REACHED();
575     UNUSED_PARAM(checkGrammar);
576     UNUSED_PARAM(misspelled);
577     UNUSED_PARAM(ungrammatical);
578     return Vector<String>();
579 #endif
580 }
581
582
583 void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
584 {
585     // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
586     // all we need to do is mark every instance.
587     int ignoredOffset;
588     findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
589 }
590
591 void TextCheckingHelper::markAllBadGrammar()
592 {
593 #ifndef BUILDING_ON_TIGER
594     // Use the "markAll" feature of findFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
595     // do is mark every instance.
596     GrammarDetail ignoredGrammarDetail;
597     int ignoredOffset;
598     findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
599 #else
600     ASSERT_NOT_REACHED();
601 #endif
602 }
603
604 }