This source file includes following definitions.
- findBadGrammars
- findMisspellings
- expandToParagraphBoundary
- m_checkingLength
- m_checkingLength
- expandRangeToNextEnd
- invalidateParagraphRangeValues
- rangeLength
- paragraphRange
- subrange
- offsetTo
- isEmpty
- offsetAsRange
- text
- checkingStart
- checkingEnd
- checkingLength
- m_range
- findFirstMisspelling
- findFirstMisspellingOrBadGrammar
- findFirstGrammarDetail
- findFirstBadGrammar
- markAllMisspellings
- markAllBadGrammar
- unifiedTextCheckerEnabled
- checkTextOfParagraph
- unifiedTextCheckerEnabled
#include "config.h"
#include "core/editing/TextCheckingHelper.h"
#include "bindings/v8/ExceptionState.h"
#include "bindings/v8/ExceptionStatePlaceholder.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentMarkerController.h"
#include "core/dom/Range.h"
#include "core/editing/TextIterator.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/VisibleUnits.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/page/SpellCheckerClient.h"
#include "platform/text/TextBreakIterator.h"
#include "platform/text/TextCheckerClient.h"
namespace WebCore {
static void findBadGrammars(TextCheckerClient& client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
{
int checkLocation = start;
int checkLength = length;
while (0 < checkLength) {
int badGrammarLocation = -1;
int badGrammarLength = 0;
Vector<GrammarDetail> badGrammarDetails;
client.checkGrammarOfString(String(text + checkLocation, checkLength), badGrammarDetails, &badGrammarLocation, &badGrammarLength);
if (!badGrammarLength)
break;
ASSERT(0 <= badGrammarLocation && badGrammarLocation <= checkLength);
ASSERT(0 < badGrammarLength && badGrammarLocation + badGrammarLength <= checkLength);
TextCheckingResult badGrammar;
badGrammar.decoration = TextDecorationTypeGrammar;
badGrammar.location = checkLocation + badGrammarLocation;
badGrammar.length = badGrammarLength;
badGrammar.details.swap(badGrammarDetails);
results.append(badGrammar);
checkLocation += (badGrammarLocation + badGrammarLength);
checkLength -= (badGrammarLocation + badGrammarLength);
}
}
static void findMisspellings(TextCheckerClient& client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results)
{
TextBreakIterator* iterator = wordBreakIterator(text + start, length);
if (!iterator)
return;
int wordStart = iterator->current();
while (0 <= wordStart) {
int wordEnd = iterator->next();
if (wordEnd < 0)
break;
int wordLength = wordEnd - wordStart;
int misspellingLocation = -1;
int misspellingLength = 0;
client.checkSpellingOfString(String(text + start + wordStart, wordLength), &misspellingLocation, &misspellingLength);
if (0 < misspellingLength) {
ASSERT(0 <= misspellingLocation && misspellingLocation <= wordLength);
ASSERT(0 < misspellingLength && misspellingLocation + misspellingLength <= wordLength);
TextCheckingResult misspelling;
misspelling.decoration = TextDecorationTypeSpelling;
misspelling.location = start + wordStart + misspellingLocation;
misspelling.length = misspellingLength;
misspelling.replacement = client.getAutoCorrectSuggestionForMisspelledWord(String(text + misspelling.location, misspelling.length));
results.append(misspelling);
}
wordStart = wordEnd;
}
}
static PassRefPtrWillBeRawPtr<Range> expandToParagraphBoundary(PassRefPtrWillBeRawPtr<Range> range)
{
RefPtrWillBeRawPtr<Range> paragraphRange = range->cloneRange(IGNORE_EXCEPTION);
setStart(paragraphRange.get(), startOfParagraph(VisiblePosition(range->startPosition())));
setEnd(paragraphRange.get(), endOfParagraph(VisiblePosition(range->endPosition())));
return paragraphRange;
}
TextCheckingParagraph::TextCheckingParagraph(PassRefPtrWillBeRawPtr<Range> checkingRange)
: m_checkingRange(checkingRange)
, m_checkingStart(-1)
, m_checkingEnd(-1)
, m_checkingLength(-1)
{
}
TextCheckingParagraph::TextCheckingParagraph(PassRefPtrWillBeRawPtr<Range> checkingRange, PassRefPtrWillBeRawPtr<Range> paragraphRange)
: m_checkingRange(checkingRange)
, m_paragraphRange(paragraphRange)
, m_checkingStart(-1)
, m_checkingEnd(-1)
, m_checkingLength(-1)
{
}
TextCheckingParagraph::~TextCheckingParagraph()
{
}
void TextCheckingParagraph::expandRangeToNextEnd()
{
ASSERT(m_checkingRange);
setEnd(paragraphRange().get(), endOfParagraph(startOfNextParagraph(VisiblePosition(paragraphRange()->startPosition()))));
invalidateParagraphRangeValues();
}
void TextCheckingParagraph::invalidateParagraphRangeValues()
{
m_checkingStart = m_checkingEnd = -1;
m_offsetAsRange = nullptr;
m_text = String();
}
int TextCheckingParagraph::rangeLength() const
{
ASSERT(m_checkingRange);
return TextIterator::rangeLength(paragraphRange().get());
}
PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::paragraphRange() const
{
ASSERT(m_checkingRange);
if (!m_paragraphRange)
m_paragraphRange = expandToParagraphBoundary(checkingRange());
return m_paragraphRange;
}
PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int characterCount) const
{
ASSERT(m_checkingRange);
return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount);
}
int TextCheckingParagraph::offsetTo(const Position& position, ExceptionState& exceptionState) const
{
ASSERT(m_checkingRange);
RefPtrWillBeRawPtr<Range> range = offsetAsRange()->cloneRange(ASSERT_NO_EXCEPTION);
range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), exceptionState);
if (exceptionState.hadException())
return 0;
return TextIterator::rangeLength(range.get());
}
bool TextCheckingParagraph::isEmpty() const
{
return isRangeEmpty() || isTextEmpty();
}
PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::offsetAsRange() const
{
ASSERT(m_checkingRange);
if (!m_offsetAsRange)
m_offsetAsRange = Range::create(paragraphRange()->startContainer()->document(), paragraphRange()->startPosition(), checkingRange()->startPosition());
return m_offsetAsRange;
}
const String& TextCheckingParagraph::text() const
{
ASSERT(m_checkingRange);
if (m_text.isEmpty())
m_text = plainText(paragraphRange().get());
return m_text;
}
int TextCheckingParagraph::checkingStart() const
{
ASSERT(m_checkingRange);
if (m_checkingStart == -1)
m_checkingStart = TextIterator::rangeLength(offsetAsRange().get());
return m_checkingStart;
}
int TextCheckingParagraph::checkingEnd() const
{
ASSERT(m_checkingRange);
if (m_checkingEnd == -1)
m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get());
return m_checkingEnd;
}
int TextCheckingParagraph::checkingLength() const
{
ASSERT(m_checkingRange);
if (-1 == m_checkingLength)
m_checkingLength = TextIterator::rangeLength(checkingRange().get());
return m_checkingLength;
}
TextCheckingHelper::TextCheckingHelper(SpellCheckerClient& client, PassRefPtrWillBeRawPtr<Range> range)
: m_client(&client)
, m_range(range)
{
ASSERT_ARG(m_range, m_range);
}
TextCheckingHelper::~TextCheckingHelper()
{
}
String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
{
WordAwareIterator it(m_range.get());
firstMisspellingOffset = 0;
String firstMisspelling;
int currentChunkOffset = 0;
while (!it.atEnd()) {
int length = it.length();
if (!(length == 1 && it.characterAt(0) == ' ')) {
int misspellingLocation = -1;
int misspellingLength = 0;
m_client->textChecker().checkSpellingOfString(it.substring(0, length), &misspellingLocation, &misspellingLength);
ASSERT(misspellingLength >= 0);
ASSERT(misspellingLocation >= -1);
ASSERT(!misspellingLength || misspellingLocation >= 0);
ASSERT(misspellingLocation < length);
ASSERT(misspellingLength <= length);
ASSERT(misspellingLocation + misspellingLength <= length);
if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < length && misspellingLength <= length && misspellingLocation + misspellingLength <= length) {
RefPtrWillBeRawPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength);
if (!firstMisspelling) {
firstMisspellingOffset = currentChunkOffset + misspellingLocation;
firstMisspelling = it.substring(misspellingLocation, misspellingLength);
firstMisspellingRange = misspellingRange;
}
misspellingRange->startContainer()->document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling);
if (!markAll)
break;
}
}
currentChunkOffset += length;
it.advance();
}
return firstMisspelling;
}
String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail)
{
if (!unifiedTextCheckerEnabled())
return "";
String firstFoundItem;
String misspelledWord;
String badGrammarPhrase;
outIsSpelling = true;
outFirstFoundOffset = 0;
outGrammarDetail.location = -1;
outGrammarDetail.length = 0;
outGrammarDetail.guesses.clear();
outGrammarDetail.userDescription = "";
RefPtrWillBeRawPtr<Range> paragraphRange = m_range->cloneRange(IGNORE_EXCEPTION);
setStart(paragraphRange.get(), startOfParagraph(VisiblePosition(m_range->startPosition())));
int totalRangeLength = TextIterator::rangeLength(paragraphRange.get());
setEnd(paragraphRange.get(), endOfParagraph(VisiblePosition(m_range->startPosition())));
RefPtrWillBeRawPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->startPosition());
int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
int totalLengthProcessed = 0;
bool firstIteration = true;
bool lastIteration = false;
while (totalLengthProcessed < totalRangeLength) {
int currentLength = TextIterator::rangeLength(paragraphRange.get());
int currentStartOffset = firstIteration ? rangeStartOffset : 0;
int currentEndOffset = currentLength;
if (inSameParagraph(VisiblePosition(paragraphRange->startPosition()), VisiblePosition(m_range->endPosition()))) {
RefPtrWillBeRawPtr<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.get());
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;
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 (checkGrammar && result->decoration == TextDecorationTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
ASSERT(result->length > 0 && result->location >= 0);
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() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) {
int spellingOffset = spellingLocation - currentStartOffset;
if (!firstIteration) {
RefPtrWillBeRawPtr<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) {
RefPtrWillBeRawPtr<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(VisiblePosition(paragraphRange->endPosition()));
setStart(paragraphRange.get(), newParagraphStart);
setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart));
firstIteration = false;
totalLengthProcessed += currentLength;
}
return firstFoundItem;
}
int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool markAll) const
{
int earliestDetailLocationSoFar = -1;
int earliestDetailIndex = -1;
for (unsigned i = 0; i < grammarDetails.size(); i++) {
const GrammarDetail* detail = &grammarDetails[i];
ASSERT(detail->length > 0 && detail->location >= 0);
int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location;
if (detailStartOffsetInParagraph < startOffset)
continue;
if (detailStartOffsetInParagraph >= endOffset)
continue;
if (markAll) {
RefPtrWillBeRawPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length);
badGrammarRange->startContainer()->document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
}
if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) {
earliestDetailIndex = i;
earliestDetailLocationSoFar = detail->location;
}
}
return earliestDetailIndex;
}
String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
{
outGrammarDetail.location = -1;
outGrammarDetail.length = 0;
outGrammarDetail.guesses.clear();
outGrammarDetail.userDescription = "";
outGrammarPhraseOffset = 0;
String firstBadGrammarPhrase;
TextCheckingParagraph paragraph(m_range);
int startOffset = 0;
while (startOffset < paragraph.checkingEnd()) {
Vector<GrammarDetail> grammarDetails;
int badGrammarPhraseLocation = -1;
int badGrammarPhraseLength = 0;
m_client->textChecker().checkGrammarOfString(paragraph.textSubstring(startOffset), grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength);
if (!badGrammarPhraseLength) {
ASSERT(badGrammarPhraseLocation == -1);
return String();
}
ASSERT(badGrammarPhraseLocation >= 0);
badGrammarPhraseLocation += startOffset;
int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll);
if (badGrammarIndex >= 0) {
ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size());
outGrammarDetail = grammarDetails[badGrammarIndex];
}
if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) {
outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart();
firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength);
if (!markAll)
break;
}
startOffset = badGrammarPhraseLocation + badGrammarPhraseLength;
}
return firstBadGrammarPhrase;
}
void TextCheckingHelper::markAllMisspellings(RefPtrWillBeRawPtr<Range>& firstMisspellingRange)
{
int ignoredOffset;
findFirstMisspelling(ignoredOffset, true, firstMisspellingRange);
}
void TextCheckingHelper::markAllBadGrammar()
{
GrammarDetail ignoredGrammarDetail;
int ignoredOffset;
findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true);
}
bool TextCheckingHelper::unifiedTextCheckerEnabled() const
{
if (!m_range)
return false;
Document& doc = m_range->ownerDocument();
return WebCore::unifiedTextCheckerEnabled(doc.frame());
}
void checkTextOfParagraph(TextCheckerClient& client, const String& text, TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results)
{
Vector<UChar> characters;
text.appendTo(characters);
unsigned length = text.length();
Vector<TextCheckingResult> spellingResult;
if (checkingTypes & TextCheckingTypeSpelling)
findMisspellings(client, characters.data(), 0, length, spellingResult);
Vector<TextCheckingResult> grammarResult;
if (checkingTypes & TextCheckingTypeGrammar) {
int grammarCheckLength = length;
for (size_t i = 0; i < spellingResult.size(); ++i) {
if (spellingResult[i].location < grammarCheckLength)
grammarCheckLength = spellingResult[i].location;
}
findBadGrammars(client, characters.data(), 0, grammarCheckLength, grammarResult);
}
if (grammarResult.size())
results.swap(grammarResult);
if (spellingResult.size()) {
if (results.isEmpty())
results.swap(spellingResult);
else
results.appendVector(spellingResult);
}
}
bool unifiedTextCheckerEnabled(const LocalFrame* frame)
{
if (!frame)
return false;
const Settings* settings = frame->settings();
if (!settings)
return false;
return settings->unifiedTextCheckerEnabled();
}
}