Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const { if (!unifiedTextCheckerEnabled()) return Vector<String>(); Vector<String> guesses; misspelled = false; ungrammatical = false; if (!m_client || !m_range || m_range->collapsed(IGNORE_EXCEPTION)) return guesses; // Expand the range to encompass entire paragraphs, since text checking needs that much context. TextCheckingParagraph paragraph(m_range); if (paragraph.isEmpty()) return guesses; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; checkTextOfParagraph(*m_client->textChecker(), paragraph.text(), checkingTypes, results); for (unsigned i = 0; i < results.size(); i++) { const TextCheckingResult* result = &results[i]; if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) { String misspelledWord = paragraph.checkingSubstring(); ASSERT(misspelledWord.length()); m_client->textChecker()->getGuessesForWord(misspelledWord, String(), guesses); m_client->updateSpellingUIWithMisspelledWord(misspelledWord); misspelled = true; return guesses; } } if (!checkGrammar) return guesses; for (unsigned i = 0; i < results.size(); i++) { const TextCheckingResult* result = &results[i]; if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) { for (unsigned j = 0; j < result->details.size(); j++) { const GrammarDetail* detail = &result->details[j]; ASSERT(detail->length > 0); ASSERT(detail->location >= 0); if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) { String badGrammarPhrase = paragraph.textSubstring(result->location, result->length); ASSERT(badGrammarPhrase.length()); for (unsigned k = 0; k < detail->guesses.size(); k++) guesses.append(detail->guesses[k]); m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail); ungrammatical = true; return guesses; } } } } return guesses; }
Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const { if (!unifiedTextCheckerEnabled()) return Vector<String>(); Vector<String> guesses; misspelled = false; ungrammatical = false; if (!m_client || !m_range || m_range->collapsed()) return guesses; // Expand the range to encompass entire paragraphs, since text checking needs that much context. TextCheckingParagraph paragraph(m_range); if (paragraph.isEmpty()) return guesses; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; VisibleSelection currentSelection; if (Frame* frame = m_range->ownerDocument().frame()) currentSelection = frame->selection().selection(); checkTextOfParagraph(*m_client->textChecker(), paragraph.text(), checkingTypes, results, currentSelection); for (auto& result : results) { if (result.type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result.location, result.length)) { String misspelledWord = paragraph.checkingSubstring(); ASSERT(misspelledWord.length()); m_client->textChecker()->getGuessesForWord(misspelledWord, String(), currentSelection, guesses); m_client->updateSpellingUIWithMisspelledWord(misspelledWord); misspelled = true; return guesses; } } if (!checkGrammar) return guesses; for (auto& result : results) { if (result.type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result.location, result.length)) { for (auto& detail : result.details) { ASSERT(detail.length > 0); ASSERT(detail.location >= 0); if (paragraph.checkingRangeMatches(result.location + detail.location, detail.length)) { String badGrammarPhrase = paragraph.textSubstring(result.location, result.length); ASSERT(badGrammarPhrase.length()); for (auto& guess : detail.guesses) guesses.append(guess); m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, detail); ungrammatical = true; return guesses; } } } } return guesses; }
void TextChecker::requestCheckingOfString(PassRefPtr<TextCheckerCompletion> completion) { #if ENABLE(SPELLCHECK) if (!completion) return; TextCheckingRequestData request = completion->textCheckingRequestData(); ASSERT(request.sequence() != unrequestedTextCheckingSequence); ASSERT(request.mask() != TextCheckingTypeNone); completion->didFinishCheckingText(checkTextOfParagraph(completion->spellDocumentTag(), request.text(), request.mask())); #else UNUSED_PARAM(completion); #endif }
void SpellChecker::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, Range* checkRange, Range* paragraphRange, bool asynchronous, int requestNumber, int* checkingLength) { TextCheckingParagraph sentenceToCheck(checkRange, paragraphRange); if (checkingLength) *checkingLength = sentenceToCheck.checkingLength(); RefPtr<SpellCheckRequest> request = SpellCheckRequest::create(resolveTextCheckingTypeMask(textCheckingOptions), TextCheckingProcessBatch, checkRange, paragraphRange, requestNumber); if (asynchronous) { m_spellCheckRequester->requestCheckingFor(request); } else { Vector<TextCheckingResult> results; checkTextOfParagraph(textChecker(), sentenceToCheck.text(), resolveTextCheckingTypeMask(textCheckingOptions), results); markAndReplaceFor(request, results); } }
String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) { if (!unifiedTextCheckerEnabled()) return ""; String firstFoundItem; String misspelledWord; String badGrammarPhrase; // Initialize out parameters; these will be updated if we find something to return. outIsSpelling = true; outFirstFoundOffset = 0; outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); outGrammarDetail.userDescription = ""; // Expand the search range to encompass entire paragraphs, since text checking needs that much context. // Determine the character offset from the start of the paragraph to the start of the original search range, // since we will want to ignore results in this area. Position paragraphStart = startOfParagraph(createVisiblePosition(m_start)).toParentAnchoredPosition(); Position paragraphEnd = m_end; int totalRangeLength = TextIterator::rangeLength(paragraphStart, paragraphEnd); paragraphEnd = endOfParagraph(createVisiblePosition(m_start)).toParentAnchoredPosition(); int rangeStartOffset = TextIterator::rangeLength(paragraphStart, m_start); int totalLengthProcessed = 0; bool firstIteration = true; bool lastIteration = false; while (totalLengthProcessed < totalRangeLength) { // Iterate through the search range by paragraphs, checking each one for spelling and grammar. int currentLength = TextIterator::rangeLength(paragraphStart, paragraphEnd); int currentStartOffset = firstIteration ? rangeStartOffset : 0; int currentEndOffset = currentLength; if (inSameParagraph(createVisiblePosition(paragraphStart), createVisiblePosition(m_end))) { // Determine the character offset from the end of the original search range to the end of the paragraph, // since we will want to ignore results in this area. currentEndOffset = TextIterator::rangeLength(paragraphStart, m_end); lastIteration = true; } if (currentStartOffset < currentEndOffset) { String paragraphString = plainText(EphemeralRange(paragraphStart, paragraphEnd)); if (paragraphString.length() > 0) { bool foundGrammar = false; int spellingLocation = 0; int grammarPhraseLocation = 0; int grammarDetailLocation = 0; unsigned grammarDetailIndex = 0; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = TextCheckingTypeSpelling | TextCheckingTypeGrammar; checkTextOfParagraph(m_client->textChecker(), paragraphString, checkingTypes, results); for (unsigned i = 0; i < results.size(); i++) { const TextCheckingResult* result = &results[i]; if (result->decoration == TextDecorationTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) { ASSERT(result->length > 0 && result->location >= 0); spellingLocation = result->location; misspelledWord = paragraphString.substring(result->location, result->length); ASSERT(misspelledWord.length()); break; } if (result->decoration == TextDecorationTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) { ASSERT(result->length > 0 && result->location >= 0); // We can't stop after the first grammar result, since there might still be a spelling result after // it begins but before the first detail in it, but we can stop if we find a second grammar result. if (foundGrammar) break; for (unsigned j = 0; j < result->details.size(); j++) { const GrammarDetail* detail = &result->details[j]; ASSERT(detail->length > 0 && detail->location >= 0); if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) { grammarDetailIndex = j; grammarDetailLocation = result->location + detail->location; foundGrammar = true; } } if (foundGrammar) { grammarPhraseLocation = result->location; outGrammarDetail = result->details[grammarDetailIndex]; badGrammarPhrase = paragraphString.substring(result->location, result->length); ASSERT(badGrammarPhrase.length()); } } } if (!misspelledWord.isEmpty() && (badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { int spellingOffset = spellingLocation - currentStartOffset; if (!firstIteration) spellingOffset += TextIterator::rangeLength(m_start, paragraphStart); outIsSpelling = true; outFirstFoundOffset = spellingOffset; firstFoundItem = misspelledWord; break; } if (!badGrammarPhrase.isEmpty()) { int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; if (!firstIteration) grammarPhraseOffset += TextIterator::rangeLength(m_start, paragraphStart); outIsSpelling = false; outFirstFoundOffset = grammarPhraseOffset; firstFoundItem = badGrammarPhrase; break; } } } if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) break; VisiblePosition newParagraphStart = startOfNextParagraph(createVisiblePosition(paragraphEnd)); paragraphStart = newParagraphStart.toParentAnchoredPosition(); paragraphEnd = endOfParagraph(newParagraphStart).toParentAnchoredPosition(); firstIteration = false; totalLengthProcessed += currentLength; } return firstFoundItem; }
String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) { if (!unifiedTextCheckerEnabled()) return ""; String firstFoundItem; String misspelledWord; String badGrammarPhrase; // Initialize out parameters; these will be updated if we find something to return. outIsSpelling = true; outFirstFoundOffset = 0; outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); outGrammarDetail.userDescription = ""; // Expand the search range to encompass entire paragraphs, since text checking needs that much context. // Determine the character offset from the start of the paragraph to the start of the original search range, // since we will want to ignore results in this area. Ref<Range> paragraphRange = m_range->cloneRange(); setStart(paragraphRange.ptr(), startOfParagraph(m_range->startPosition())); int totalRangeLength = TextIterator::rangeLength(paragraphRange.ptr()); setEnd(paragraphRange.ptr(), endOfParagraph(m_range->startPosition())); Ref<Range> offsetAsRange = Range::create(paragraphRange->startContainer().document(), paragraphRange->startPosition(), m_range->startPosition()); int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.ptr()); int totalLengthProcessed = 0; bool firstIteration = true; bool lastIteration = false; while (totalLengthProcessed < totalRangeLength) { // Iterate through the search range by paragraphs, checking each one for spelling and grammar. int currentLength = TextIterator::rangeLength(paragraphRange.ptr()); int currentStartOffset = firstIteration ? rangeStartOffset : 0; int currentEndOffset = currentLength; if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) { // Determine the character offset from the end of the original search range to the end of the paragraph, // since we will want to ignore results in this area. RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer().document(), paragraphRange->startPosition(), m_range->endPosition()); currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get()); lastIteration = true; } if (currentStartOffset < currentEndOffset) { String paragraphString = plainText(paragraphRange.ptr()); if (paragraphString.length() > 0) { bool foundGrammar = false; int spellingLocation = 0; int grammarPhraseLocation = 0; int grammarDetailLocation = 0; unsigned grammarDetailIndex = 0; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; VisibleSelection currentSelection; if (Frame* frame = paragraphRange->ownerDocument().frame()) currentSelection = frame->selection().selection(); checkTextOfParagraph(*m_client->textChecker(), paragraphString, checkingTypes, results, currentSelection); for (auto& result : results) { if (result.type == TextCheckingTypeSpelling && result.location >= currentStartOffset && result.location + result.length <= currentEndOffset) { ASSERT(result.length > 0); ASSERT(result.location >= 0); spellingLocation = result.location; misspelledWord = paragraphString.substring(result.location, result.length); ASSERT(misspelledWord.length()); break; } if (checkGrammar && result.type == TextCheckingTypeGrammar && result.location < currentEndOffset && result.location + result.length > currentStartOffset) { ASSERT(result.length > 0); ASSERT(result.location >= 0); // We can't stop after the first grammar result, since there might still be a spelling result after // it begins but before the first detail in it, but we can stop if we find a second grammar result. if (foundGrammar) break; for (unsigned j = 0; j < result.details.size(); j++) { const GrammarDetail* detail = &result.details[j]; ASSERT(detail->length > 0); ASSERT(detail->location >= 0); if (result.location + detail->location >= currentStartOffset && result.location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result.location + detail->location < grammarDetailLocation)) { grammarDetailIndex = j; grammarDetailLocation = result.location + detail->location; foundGrammar = true; } } if (foundGrammar) { grammarPhraseLocation = result.location; outGrammarDetail = result.details[grammarDetailIndex]; badGrammarPhrase = paragraphString.substring(result.location, result.length); ASSERT(badGrammarPhrase.length()); } } } if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { int spellingOffset = spellingLocation - currentStartOffset; if (!firstIteration) { RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer().document(), m_range->startPosition(), paragraphRange->startPosition()); spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); } outIsSpelling = true; outFirstFoundOffset = spellingOffset; firstFoundItem = misspelledWord; break; } if (checkGrammar && !badGrammarPhrase.isEmpty()) { int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; if (!firstIteration) { RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer().document(), m_range->startPosition(), paragraphRange->startPosition()); grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); } outIsSpelling = false; outFirstFoundOffset = grammarPhraseOffset; firstFoundItem = badGrammarPhrase; break; } } } if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) break; VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition()); setStart(paragraphRange.ptr(), newParagraphStart); setEnd(paragraphRange.ptr(), endOfParagraph(newParagraphStart)); firstIteration = false; totalLengthProcessed += currentLength; } return firstFoundItem; }