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