2
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions
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.
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.
28
#include "TextCheckingHelper.h"
31
#include "DocumentMarkerController.h"
35
#include "TextBreakIterator.h"
36
#include "TextCheckerClient.h"
37
#include "TextIterator.h"
38
#include "VisiblePosition.h"
39
#include "visible_units.h"
43
#if !USE(UNIFIED_TEXT_CHECKING)
44
static void findBadGrammars(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
46
ASSERT(WTF_USE_GRAMMAR_CHECKING);
48
int checkLocation = start;
49
int checkLength = length;
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)
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);
67
checkLocation += (badGrammarLocation + badGrammarLength);
68
checkLength -= (badGrammarLocation + badGrammarLength);
72
static void findMisspellings(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
74
TextBreakIterator* iterator = wordBreakIterator(text + start, length);
77
int wordStart = textBreakCurrent(iterator);
78
while (0 <= wordStart) {
79
int wordEnd = textBreakNext(iterator);
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);
102
static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range)
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;
111
TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange)
112
: m_checkingRange(checkingRange)
113
, m_checkingStart(-1)
115
, m_checkingLength(-1)
119
TextCheckingParagraph::TextCheckingParagraph(PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange)
120
: m_checkingRange(checkingRange)
121
, m_paragraphRange(paragraphRange)
122
, m_checkingStart(-1)
124
, m_checkingLength(-1)
128
TextCheckingParagraph::~TextCheckingParagraph()
132
void TextCheckingParagraph::expandRangeToNextEnd()
134
ASSERT(m_checkingRange);
135
setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(paragraphRange()->startPosition())));
136
invalidateParagraphRangeValues();
139
void TextCheckingParagraph::invalidateParagraphRangeValues()
141
m_checkingStart = m_checkingEnd = -1;
146
int TextCheckingParagraph::rangeLength() const
148
ASSERT(m_checkingRange);
149
return TextIterator::rangeLength(paragraphRange().get());
152
PassRefPtr<Range> TextCheckingParagraph::paragraphRange() const
154
ASSERT(m_checkingRange);
155
if (!m_paragraphRange)
156
m_paragraphRange = expandToParagraphBoundary(checkingRange());
157
return m_paragraphRange;
160
PassRefPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
162
ASSERT(m_checkingRange);
163
return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
166
int TextCheckingParagraph::offsetTo(const Position& position, ExceptionCode& ec) const
168
ASSERT(m_checkingRange);
169
RefPtr<Range> range = offsetAsRange()->cloneRange(ASSERT_NO_EXCEPTION);
170
range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), ec);
173
return TextIterator::rangeLength(range.get());
176
bool TextCheckingParagraph::isEmpty() const
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();
183
PassRefPtr<Range> TextCheckingParagraph::offsetAsRange() const
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());
191
return m_offsetAsRange;
194
const String& TextCheckingParagraph::text() const
196
ASSERT(m_checkingRange);
197
if (m_text.isEmpty())
198
m_text = plainText(paragraphRange().get());
202
int TextCheckingParagraph::checkingStart() const
204
ASSERT(m_checkingRange);
205
if (m_checkingStart == -1)
206
m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
207
return m_checkingStart;
210
int TextCheckingParagraph::checkingEnd() const
212
ASSERT(m_checkingRange);
213
if (m_checkingEnd == -1)
214
m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
215
return m_checkingEnd;
218
int TextCheckingParagraph::checkingLength() const
220
ASSERT(m_checkingRange);
221
if (-1 == m_checkingLength)
222
m_checkingLength = TextIterator::rangeLength(checkingRange().get());
223
return m_checkingLength;
226
TextCheckingHelper::TextCheckingHelper(EditorClient* client, PassRefPtr<Range> range)
230
ASSERT_ARG(m_client, m_client);
231
ASSERT_ARG(m_range, m_range);
234
TextCheckingHelper::~TextCheckingHelper()
238
String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
240
WordAwareIterator it(m_range.get());
241
firstMisspellingOffset = 0;
243
String firstMisspelling;
244
int currentChunkOffset = 0;
246
while (!it.atEnd()) {
247
const UChar* chars = it.characters();
248
int len = it.length();
250
// Skip some work for one-space-char hunks
251
if (!(len == 1 && chars[0] == ' ')) {
253
int misspellingLocation = -1;
254
int misspellingLength = 0;
255
m_client->textChecker()->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength);
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);
267
if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
269
// Compute range of misspelled word
270
RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
272
// Remember first-encountered misspelling and its offset.
273
if (!firstMisspelling) {
274
firstMisspellingOffset = currentChunkOffset + misspellingLocation;
275
firstMisspelling = String(chars + misspellingLocation, misspellingLength);
276
firstMisspellingRange = misspellingRange;
279
// Store marker for misspelled word.
280
ExceptionCode ec = 0;
281
misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
284
// Bail out if we're marking only the first misspelling, and not all instances.
290
currentChunkOffset += len;
294
return firstMisspelling;
297
String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
299
if (!unifiedTextCheckerEnabled())
302
String firstFoundItem;
303
String misspelledWord;
304
String badGrammarPhrase;
305
ExceptionCode ec = 0;
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 = "";
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()));
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;
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;
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;
350
Vector<TextCheckingResult> results;
351
TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
352
checkTextOfParagraph(m_client->textChecker(), paragraphString.characters(), paragraphString.length(), checkingTypes, results);
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());
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.
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;
379
grammarPhraseLocation = result->location;
380
outGrammarDetail = result->details[grammarDetailIndex];
381
badGrammarPhrase = paragraphString.substring(result->location, result->length);
382
ASSERT(badGrammarPhrase.length());
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());
393
outIsSpelling = true;
394
outFirstFoundOffset = spellingOffset;
395
firstFoundItem = misspelledWord;
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());
404
outIsSpelling = false;
405
outFirstFoundOffset = grammarPhraseOffset;
406
firstFoundItem = badGrammarPhrase;
411
if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength)
413
VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition());
414
setStart(paragraphRange.get(), newParagraphStart);
415
setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
416
firstIteration = false;
417
totalLengthProcessed += currentLength;
419
return firstFoundItem;
422
int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, int startOffset, int endOffset, bool markAll)
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);
433
int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
435
// Skip this detail if it starts before the original search range
436
if (detailStartOffsetInParagraph < startOffset)
439
// Skip this detail if it starts after the original search range
440
if (detailStartOffsetInParagraph >= endOffset)
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);
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;
457
return earliestDetailIndex;
459
ASSERT_NOT_REACHED();
460
UNUSED_PARAM(grammarDetails);
461
UNUSED_PARAM(badGrammarPhraseLocation);
462
UNUSED_PARAM(startOffset);
463
UNUSED_PARAM(endOffset);
464
UNUSED_PARAM(markAll);
469
String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
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;
479
String firstBadGrammarPhrase;
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);
486
// Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
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);
494
if (!badGrammarPhraseLength) {
495
ASSERT(badGrammarPhraseLocation == -1);
499
ASSERT(badGrammarPhraseLocation >= 0);
500
badGrammarPhraseLocation += startOffset;
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];
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);
516
// Found one. We're done now, unless we're marking each instance.
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;
526
return firstBadGrammarPhrase;
530
bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const
532
ASSERT(WTF_USE_GRAMMAR_CHECKING);
537
if (!m_range || m_range->collapsed(ec))
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;
546
GrammarDetail grammarDetail;
547
String badGrammarPhrase = const_cast<TextCheckingHelper*>(this)->findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false);
549
// No bad grammar in these parts at all.
550
if (badGrammarPhrase.isEmpty())
553
// Bad grammar, but phrase (e.g. sentence) starts beyond start of range.
554
if (grammarPhraseOffset > 0)
557
ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0);
559
// Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range
560
if (grammarDetail.location + grammarPhraseOffset)
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()))
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);
576
Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const
578
if (!unifiedTextCheckerEnabled())
579
return Vector<String>();
581
Vector<String> guesses;
584
ungrammatical = false;
586
if (!m_client || !m_range || m_range->collapsed(ec))
589
// Expand the range to encompass entire paragraphs, since text checking needs that much context.
590
TextCheckingParagraph paragraph(m_range);
591
if (paragraph.isEmpty())
594
Vector<TextCheckingResult> results;
595
TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
596
checkTextOfParagraph(m_client->textChecker(), paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results);
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);
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;
635
void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange)
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.
640
findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
643
void TextCheckingHelper::markAllBadGrammar()
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;
650
findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
653
bool TextCheckingHelper::unifiedTextCheckerEnabled() const
658
Document* doc = m_range->ownerDocument();
662
return WebCore::unifiedTextCheckerEnabled(doc->frame());
665
void checkTextOfParagraph(TextCheckerClient* client, const UChar* text, int length,
666
TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
668
#if USE(UNIFIED_TEXT_CHECKING)
669
client->checkTextOfParagraph(text, length, checkingTypes, results);
671
Vector<TextCheckingResult> spellingResult;
672
if (checkingTypes & TextCheckingTypeSpelling)
673
findMisspellings(client, text, 0, length, spellingResult);
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;
684
findBadGrammars(client, text, 0, grammarCheckLength, grammarResult);
687
if (grammarResult.size())
688
results.swap(grammarResult);
690
if (spellingResult.size()) {
691
if (results.isEmpty())
692
results.swap(spellingResult);
694
results.append(spellingResult);
699
bool unifiedTextCheckerEnabled(const Frame* frame)
704
const Settings* settings = frame->settings();
708
return settings->unifiedTextCheckerEnabled();