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 "Document.h"
31#include "DocumentMarkerController.h"
32#include "Frame.h"
33#include "Range.h"
34#include "Settings.h"
35#include "TextBreakIterator.h"
36#include "TextCheckerClient.h"
37#include "TextIterator.h"
38#include "VisiblePosition.h"
39#include "VisibleUnits.h"
40
41namespace WebCore {
42
43#if !USE(UNIFIED_TEXT_CHECKING)
44
45#if USE(GRAMMAR_CHECKING)
46static void findBadGrammars(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
47{
48    int checkLocation = start;
49    int checkLength = length;
50
51    while (0 < checkLength) {
52        int badGrammarLocation = -1;
53        int badGrammarLength = 0;
54        Vector<GrammarDetail> badGrammarDetails;
55        client->checkGrammarOfString(text + checkLocation, checkLength, badGrammarDetails, &badGrammarLocation, &badGrammarLength);
56        if (!badGrammarLength)
57            break;
58        ASSERT(0 <= badGrammarLocation && badGrammarLocation <= checkLength);
59        ASSERT(0 < badGrammarLength && badGrammarLocation + badGrammarLength <= checkLength);
60        TextCheckingResult badGrammar;
61        badGrammar.type = TextCheckingTypeGrammar;
62        badGrammar.location = checkLocation + badGrammarLocation;
63        badGrammar.length = badGrammarLength;
64        badGrammar.details.swap(badGrammarDetails);
65        results.append(badGrammar);
66
67        checkLocation += (badGrammarLocation + badGrammarLength);
68        checkLength -= (badGrammarLocation + badGrammarLength);
69    }
70}
71#endif
72
73static void findMisspellings(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
74{
75    TextBreakIterator* iterator = wordBreakIterator(text + start, length);
76    if (!iterator)
77        return;
78    int wordStart = textBreakCurrent(iterator);
79    while (0 <= wordStart) {
80        int wordEnd = textBreakNext(iterator);
81        if (wordEnd < 0)
82            break;
83        int wordLength = wordEnd - wordStart;
84        int misspellingLocation = -1;
85        int misspellingLength = 0;
86        client->checkSpellingOfString(text + start + wordStart, wordLength, &misspellingLocation, &misspellingLength);
87        if (0 < misspellingLength) {
88            ASSERT(0 <= misspellingLocation && misspellingLocation <= wordLength);
89            ASSERT(0 < misspellingLength && misspellingLocation + misspellingLength <= wordLength);
90            TextCheckingResult misspelling;
91            misspelling.type = TextCheckingTypeSpelling;
92            misspelling.location = start + wordStart + misspellingLocation;
93            misspelling.length = misspellingLength;
94            misspelling.replacement = client->getAutoCorrectSuggestionForMisspelledWord(String(text + misspelling.location, misspelling.length));
95            results.append(misspelling);
96        }
97
98        wordStart = wordEnd;
99    }
100}
101#endif
102
103static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range)
104{
105    RefPtr<Range> paragraphRange = range->cloneRange(IGNORE_EXCEPTION);
106    setStart(paragraphRange.get(), startOfParagraph(range->startPosition()));
107    setEnd(paragraphRange.get(), endOfParagraph(range->endPosition()));
108    return paragraphRange;
109}
110
111TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange)
112    : m_checkingRange(checkingRange)
113    , m_checkingStart(-1)
114    , m_checkingEnd(-1)
115    , m_checkingLength(-1)
116{
117}
118
119TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange)
120    : m_checkingRange(checkingRange)
121    , m_paragraphRange(paragraphRange)
122    , m_checkingStart(-1)
123    , m_checkingEnd(-1)
124    , m_checkingLength(-1)
125{
126}
127
128TextCheckingParagraph::~TextCheckingParagraph()
129{
130}
131
132void TextCheckingParagraph::expandRangeToNextEnd()
133{
134    ASSERT(m_checkingRange);
135    setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(paragraphRange()->startPosition())));
136    invalidateParagraphRangeValues();
137}
138
139void TextCheckingParagraph::invalidateParagraphRangeValues()
140{
141    m_checkingStart = m_checkingEnd = -1;
142    m_offsetAsRange = 0;
143    m_text = String();
144}
145
146int TextCheckingParagraph::rangeLength() const
147{
148    ASSERT(m_checkingRange);
149    return TextIterator::rangeLength(paragraphRange().get());
150}
151
152PassRefPtr<Range> TextCheckingParagraph::paragraphRange() const
153{
154    ASSERT(m_checkingRange);
155    if (!m_paragraphRange)
156        m_paragraphRange = expandToParagraphBoundary(checkingRange());
157    return m_paragraphRange;
158}
159
160PassRefPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
161{
162    ASSERT(m_checkingRange);
163    return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
164}
165
166int TextCheckingParagraph::offsetTo(const Position& position, ExceptionCode& ec) const
167{
168    ASSERT(m_checkingRange);
169    RefPtr<Range> range = offsetAsRange()->cloneRange(ASSERT_NO_EXCEPTION);
170    range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), ec);
171    if (ec)
172        return 0;
173    return TextIterator::rangeLength(range.get());
174}
175
176bool TextCheckingParagraph::isEmpty() const
177{
178    // Both predicates should have same result, but we check both just for sure.
179    // We need to investigate to remove this redundancy.
180    return isRangeEmpty() || isTextEmpty();
181}
182
183PassRefPtr<Range> TextCheckingParagraph::offsetAsRange() const
184{
185    ASSERT(m_checkingRange);
186    if (!m_offsetAsRange)
187        m_offsetAsRange = Range::create(paragraphRange()->startContainer()->document(), paragraphRange()->startPosition(), checkingRange()->startPosition());
188
189    return m_offsetAsRange;
190}
191
192const String& TextCheckingParagraph::text() const
193{
194    ASSERT(m_checkingRange);
195    if (m_text.isEmpty())
196        m_text = plainText(paragraphRange().get());
197    return m_text;
198}
199
200int TextCheckingParagraph::checkingStart() const
201{
202    ASSERT(m_checkingRange);
203    if (m_checkingStart == -1)
204        m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
205    return m_checkingStart;
206}
207
208int TextCheckingParagraph::checkingEnd() const
209{
210    ASSERT(m_checkingRange);
211    if (m_checkingEnd == -1)
212        m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
213    return m_checkingEnd;
214}
215
216int TextCheckingParagraph::checkingLength() const
217{
218    ASSERT(m_checkingRange);
219    if (-1 == m_checkingLength)
220        m_checkingLength = TextIterator::rangeLength(checkingRange().get());
221    return m_checkingLength;
222}
223
224TextCheckingHelper::TextCheckingHelper(EditorClient* client, PassRefPtr<Range> range)
225    : m_client(client)
226    , m_range(range)
227{
228    ASSERT_ARG(m_client, m_client);
229    ASSERT_ARG(m_range, m_range);
230}
231
232TextCheckingHelper::~TextCheckingHelper()
233{
234}
235
236String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
237{
238    WordAwareIterator it(m_range.get());
239    firstMisspellingOffset = 0;
240
241    String firstMisspelling;
242    int currentChunkOffset = 0;
243
244    while (!it.atEnd()) {
245        const UChar* chars = it.characters();
246        int len = it.length();
247
248        // Skip some work for one-space-char hunks
249        if (!(len == 1 && chars[0] == ' ')) {
250
251            int misspellingLocation = -1;
252            int misspellingLength = 0;
253            m_client->textChecker()->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
254
255            // 5490627 shows that there was some code path here where the String constructor below crashes.
256            // We don't know exactly what combination of bad input caused this, so we're making this much
257            // more robust against bad input on release builds.
258            ASSERT(misspellingLength >= 0);
259            ASSERT(misspellingLocation >= -1);
260            ASSERT(!misspellingLength || misspellingLocation >= 0);
261            ASSERT(misspellingLocation < len);
262            ASSERT(misspellingLength <= len);
263            ASSERT(misspellingLocation + misspellingLength <= len);
264
265            if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
266
267                // Compute range of misspelled word
268                RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
269
270                // Remember first-encountered misspelling and its offset.
271                if (!firstMisspelling) {
272                    firstMisspellingOffset = currentChunkOffset + misspellingLocation;
273                    firstMisspelling = String(chars + misspellingLocation, misspellingLength);
274                    firstMisspellingRange = misspellingRange;
275                }
276
277                // Store marker for misspelled word.
278                misspellingRange->startContainer()->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
279
280                // Bail out if we're marking only the first misspelling, and not all instances.
281                if (!markAll)
282                    break;
283            }
284        }
285
286        currentChunkOffset += len;
287        it.advance();
288    }
289
290    return firstMisspelling;
291}
292
293String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
294{
295    if (!unifiedTextCheckerEnabled())
296        return "";
297
298    String firstFoundItem;
299    String misspelledWord;
300    String badGrammarPhrase;
301
302    // Initialize out parameters; these will be updated if we find something to return.
303    outIsSpelling = true;
304    outFirstFoundOffset = 0;
305    outGrammarDetail.location = -1;
306    outGrammarDetail.length = 0;
307    outGrammarDetail.guesses.clear();
308    outGrammarDetail.userDescription = "";
309
310    // Expand the search range to encompass entire paragraphs, since text checking needs that much context.
311    // Determine the character offset from the start of the paragraph to the start of the original search range,
312    // since we will want to ignore results in this area.
313    RefPtr<Range> paragraphRange = m_range->cloneRange(IGNORE_EXCEPTION);
314    setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition()));
315    int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
316    setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition()));
317
318    RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->startPosition());
319    int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
320    int totalLengthProcessed = 0;
321
322    bool firstIteration = true;
323    bool lastIteration = false;
324    while (totalLengthProcessed < totalRangeLength) {
325        // Iterate through the search range by paragraphs, checking each one for spelling and grammar.
326        int currentLength = TextIterator::rangeLength(paragraphRange.get());
327        int currentStartOffset = firstIteration ? rangeStartOffset : 0;
328        int currentEndOffset = currentLength;
329        if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) {
330            // Determine the character offset from the end of the original search range to the end of the paragraph,
331            // since we will want to ignore results in this area.
332            RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->endPosition());
333            currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get());
334            lastIteration = true;
335        }
336        if (currentStartOffset < currentEndOffset) {
337            String paragraphString = plainText(paragraphRange.get());
338            if (paragraphString.length() > 0) {
339                bool foundGrammar = false;
340                int spellingLocation = 0;
341                int grammarPhraseLocation = 0;
342                int grammarDetailLocation = 0;
343                unsigned grammarDetailIndex = 0;
344
345                Vector<TextCheckingResult> results;
346                TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
347                checkTextOfParagraph(m_client->textChecker(), paragraphString.characters(), paragraphString.length(), checkingTypes, results);
348
349                for (unsigned i = 0; i < results.size(); i++) {
350                    const TextCheckingResult* result = &results[i];
351                    if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
352                        ASSERT(result->length > 0 && result->location >= 0);
353                        spellingLocation = result->location;
354                        misspelledWord = paragraphString.substring(result->location, result->length);
355                        ASSERT(misspelledWord.length());
356                        break;
357                    }
358                    if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
359                        ASSERT(result->length > 0 && result->location >= 0);
360                        // We can't stop after the first grammar result, since there might still be a spelling result after
361                        // it begins but before the first detail in it, but we can stop if we find a second grammar result.
362                        if (foundGrammar)
363                            break;
364                        for (unsigned j = 0; j < result->details.size(); j++) {
365                            const GrammarDetail* detail = &result->details[j];
366                            ASSERT(detail->length > 0 && detail->location >= 0);
367                            if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) {
368                                grammarDetailIndex = j;
369                                grammarDetailLocation = result->location + detail->location;
370                                foundGrammar = true;
371                            }
372                        }
373                        if (foundGrammar) {
374                            grammarPhraseLocation = result->location;
375                            outGrammarDetail = result->details[grammarDetailIndex];
376                            badGrammarPhrase = paragraphString.substring(result->location, result->length);
377                            ASSERT(badGrammarPhrase.length());
378                        }
379                    }
380                }
381
382                if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
383                    int spellingOffset = spellingLocation - currentStartOffset;
384                    if (!firstIteration) {
385                        RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition());
386                        spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
387                    }
388                    outIsSpelling = true;
389                    outFirstFoundOffset = spellingOffset;
390                    firstFoundItem = misspelledWord;
391                    break;
392                }
393                if (checkGrammar && !badGrammarPhrase.isEmpty()) {
394                    int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset;
395                    if (!firstIteration) {
396                        RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition());
397                        grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get());
398                    }
399                    outIsSpelling = false;
400                    outFirstFoundOffset = grammarPhraseOffset;
401                    firstFoundItem = badGrammarPhrase;
402                    break;
403                }
404            }
405        }
406        if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
407            break;
408        VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
409        setStart(paragraphRange.get(), newParagraphStart);
410        setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
411        firstIteration = false;
412        totalLengthProcessed += currentLength;
413    }
414    return firstFoundItem;
415}
416
417#if USE(GRAMMAR_CHECKING)
418int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, int startOffset, int endOffset, bool markAll)
419{
420    // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
421    // Optionally add a DocumentMarker for each detail in the range.
422    int earliestDetailLocationSoFar = -1;
423    int earliestDetailIndex = -1;
424    for (unsigned i = 0; i < grammarDetails.size(); i++) {
425        const GrammarDetail* detail = &grammarDetails[i];
426        ASSERT(detail->length > 0 && detail->location >= 0);
427
428        int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
429
430        // Skip this detail if it starts before the original search range
431        if (detailStartOffsetInParagraph < startOffset)
432            continue;
433
434        // Skip this detail if it starts after the original search range
435        if (detailStartOffsetInParagraph >= endOffset)
436            continue;
437
438        if (markAll) {
439            RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
440            badGrammarRange->startContainer()->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
441        }
442
443        // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
444        if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
445            earliestDetailIndex = i;
446            earliestDetailLocationSoFar = detail->location;
447        }
448    }
449
450    return earliestDetailIndex;
451}
452
453String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
454{
455    // Initialize out parameters; these will be updated if we find something to return.
456    outGrammarDetail.location = -1;
457    outGrammarDetail.length = 0;
458    outGrammarDetail.guesses.clear();
459    outGrammarDetail.userDescription = "";
460    outGrammarPhraseOffset = 0;
461
462    String firstBadGrammarPhrase;
463
464    // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
465    // Determine the character offset from the start of the paragraph to the start of the original search range,
466    // since we will want to ignore results in this area.
467    TextCheckingParagraph paragraph(m_range);
468
469    // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
470    int startOffset = 0;
471    while (startOffset < paragraph.checkingEnd()) {
472        Vector<GrammarDetail> grammarDetails;
473        int badGrammarPhraseLocation = -1;
474        int badGrammarPhraseLength = 0;
475        m_client->textChecker()->checkGrammarOfString(paragraph.textCharacters() + startOffset, paragraph.textLength() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
476
477        if (!badGrammarPhraseLength) {
478            ASSERT(badGrammarPhraseLocation == -1);
479            return String();
480        }
481
482        ASSERT(badGrammarPhraseLocation >= 0);
483        badGrammarPhraseLocation += startOffset;
484
485
486        // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
487        int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, badGrammarPhraseLength, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
488        if (badGrammarIndex >= 0) {
489            ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
490            outGrammarDetail = grammarDetails[badGrammarIndex];
491        }
492
493        // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
494        // kept going so we could mark all instances).
495        if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
496            outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
497            firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
498
499            // Found one. We're done now, unless we're marking each instance.
500            if (!markAll)
501                break;
502        }
503
504        // These results were all between the start of the paragraph and the start of the search range; look
505        // beyond this phrase.
506        startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
507    }
508
509    return firstBadGrammarPhrase;
510}
511
512
513bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const
514{
515    if (!m_client)
516        return false;
517
518    if (!m_range || m_range->collapsed(IGNORE_EXCEPTION))
519        return false;
520
521    // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous
522    // to isSelectionMisspelled. It's not good enough for there to be some bad grammar somewhere in the range,
523    // or overlapping the range; the ranges must exactly match.
524    guessesVector.clear();
525    int grammarPhraseOffset;
526
527    GrammarDetail grammarDetail;
528    String badGrammarPhrase = const_cast<TextCheckingHelper*>(this)->findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
529
530    // No bad grammar in these parts at all.
531    if (badGrammarPhrase.isEmpty())
532        return false;
533
534    // Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
535    if (grammarPhraseOffset > 0)
536        return false;
537
538    ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
539
540    // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
541    if (grammarDetail.location + grammarPhraseOffset)
542        return false;
543
544    // Bad grammar at start of range, but end of bad grammar is before or after end of range
545    if (grammarDetail.length != TextIterator::rangeLength(m_range.get()))
546        return false;
547
548    // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
549    // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
550    // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
551    // or a grammar error.
552    m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
553
554    return true;
555}
556#endif
557
558Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const
559{
560    if (!unifiedTextCheckerEnabled())
561        return Vector<String>();
562
563    Vector<String> guesses;
564    misspelled = false;
565    ungrammatical = false;
566
567    if (!m_client || !m_range || m_range->collapsed(IGNORE_EXCEPTION))
568        return guesses;
569
570    // Expand the range to encompass entire paragraphs, since text checking needs that much context.
571    TextCheckingParagraph paragraph(m_range);
572    if (paragraph.isEmpty())
573        return guesses;
574
575    Vector<TextCheckingResult> results;
576    TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
577    checkTextOfParagraph(m_client->textChecker(), paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results);
578
579    for (unsigned i = 0; i < results.size(); i++) {
580        const TextCheckingResult* result = &results[i];
581        if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) {
582            String misspelledWord = paragraph.checkingSubstring();
583            ASSERT(misspelledWord.length());
584            m_client->textChecker()->getGuessesForWord(misspelledWord, String(), guesses);
585            m_client->updateSpellingUIWithMisspelledWord(misspelledWord);
586            misspelled = true;
587            return guesses;
588        }
589    }
590
591    if (!checkGrammar)
592        return guesses;
593
594    for (unsigned i = 0; i < results.size(); i++) {
595        const TextCheckingResult* result = &results[i];
596        if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) {
597            for (unsigned j = 0; j < result->details.size(); j++) {
598                const GrammarDetail* detail = &result->details[j];
599                ASSERT(detail->length > 0 && detail->location >= 0);
600                if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) {
601                    String badGrammarPhrase = paragraph.textSubstring(result->location, result->length);
602                    ASSERT(badGrammarPhrase.length());
603                    for (unsigned k = 0; k < detail->guesses.size(); k++)
604                        guesses.append(detail->guesses[k]);
605                    m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail);
606                    ungrammatical = true;
607                    return guesses;
608                }
609            }
610        }
611    }
612    return guesses;
613}
614
615
616void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
617{
618    // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter";
619    // all we need to do is mark every instance.
620    int ignoredOffset;
621    findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
622}
623
624#if USE(GRAMMAR_CHECKING)
625void TextCheckingHelper::markAllBadGrammar()
626{
627    // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return value and "out parameters"; all we need to
628    // do is mark every instance.
629    GrammarDetail ignoredGrammarDetail;
630    int ignoredOffset;
631    findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
632}
633#endif
634
635bool TextCheckingHelper::unifiedTextCheckerEnabled() const
636{
637    if (!m_range)
638        return false;
639
640    Document* doc = m_range->ownerDocument();
641    if (!doc)
642        return false;
643
644    return WebCore::unifiedTextCheckerEnabled(doc->frame());
645}
646
647void checkTextOfParagraph(TextCheckerClient* client, const UChar* text, int length,
648                          TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
649{
650#if USE(UNIFIED_TEXT_CHECKING)
651    client->checkTextOfParagraph(text, length, checkingTypes, results);
652#else
653    Vector<TextCheckingResult> spellingResult;
654    if (checkingTypes & TextCheckingTypeSpelling)
655        findMisspellings(client, text, 0, length, spellingResult);
656
657#if USE(GRAMMAR_CHECKING)
658    Vector<TextCheckingResult> grammarResult;
659    if (checkingTypes & TextCheckingTypeGrammar) {
660        // Only checks grammartical error before the first misspellings
661        int grammarCheckLength = length;
662        for (size_t i = 0; i < spellingResult.size(); ++i) {
663            if (spellingResult[i].location < grammarCheckLength)
664                grammarCheckLength = spellingResult[i].location;
665        }
666
667        findBadGrammars(client, text, 0, grammarCheckLength, grammarResult);
668    }
669
670    if (grammarResult.size())
671        results.swap(grammarResult);
672#endif
673
674    if (spellingResult.size()) {
675        if (results.isEmpty())
676            results.swap(spellingResult);
677        else
678            results.appendVector(spellingResult);
679    }
680#endif
681}
682
683bool unifiedTextCheckerEnabled(const Frame* frame)
684{
685    if (!frame)
686        return false;
687
688    const Settings* settings = frame->settings();
689    if (!settings)
690        return false;
691
692    return settings->unifiedTextCheckerEnabled();
693}
694
695}
696