~ubuntu-branches/ubuntu/raring/qtwebkit-source/raring-proposed

« back to all changes in this revision

Viewing changes to Source/WebCore/editing/TextCheckingHelper.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-02-18 14:24:18 UTC
  • Revision ID: package-import@ubuntu.com-20130218142418-eon0jmjg3nj438uy
Tags: upstream-2.3
ImportĀ upstreamĀ versionĀ 2.3

Show diffs side-by-side

added added

removed removed

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