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