| OLD | NEW |
| (Empty) |
| 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 "core/editing/TextCheckingHelper.h" | |
| 29 | |
| 30 #include "bindings/core/v8/ExceptionState.h" | |
| 31 #include "bindings/core/v8/ExceptionStatePlaceholder.h" | |
| 32 #include "core/dom/Document.h" | |
| 33 #include "core/dom/Range.h" | |
| 34 #include "core/editing/VisiblePosition.h" | |
| 35 #include "core/editing/VisibleUnits.h" | |
| 36 #include "core/editing/iterators/CharacterIterator.h" | |
| 37 #include "core/editing/iterators/WordAwareIterator.h" | |
| 38 #include "core/editing/markers/DocumentMarkerController.h" | |
| 39 #include "core/frame/LocalFrame.h" | |
| 40 #include "core/frame/Settings.h" | |
| 41 #include "core/page/SpellCheckerClient.h" | |
| 42 #include "platform/text/TextBreakIterator.h" | |
| 43 #include "platform/text/TextCheckerClient.h" | |
| 44 | |
| 45 namespace blink { | |
| 46 | |
| 47 static void findBadGrammars(TextCheckerClient& client, const UChar* text, int st
art, int length, Vector<TextCheckingResult>& results) | |
| 48 { | |
| 49 int checkLocation = start; | |
| 50 int checkLength = length; | |
| 51 | |
| 52 while (0 < checkLength) { | |
| 53 int badGrammarLocation = -1; | |
| 54 int badGrammarLength = 0; | |
| 55 Vector<GrammarDetail> badGrammarDetails; | |
| 56 client.checkGrammarOfString(String(text + checkLocation, checkLength), b
adGrammarDetails, &badGrammarLocation, &badGrammarLength); | |
| 57 if (!badGrammarLength) | |
| 58 break; | |
| 59 ASSERT(0 <= badGrammarLocation && badGrammarLocation <= checkLength); | |
| 60 ASSERT(0 < badGrammarLength && badGrammarLocation + badGrammarLength <=
checkLength); | |
| 61 TextCheckingResult badGrammar; | |
| 62 badGrammar.decoration = TextDecorationTypeGrammar; | |
| 63 badGrammar.location = checkLocation + badGrammarLocation; | |
| 64 badGrammar.length = badGrammarLength; | |
| 65 badGrammar.details.swap(badGrammarDetails); | |
| 66 results.append(badGrammar); | |
| 67 | |
| 68 checkLocation += (badGrammarLocation + badGrammarLength); | |
| 69 checkLength -= (badGrammarLocation + badGrammarLength); | |
| 70 } | |
| 71 } | |
| 72 | |
| 73 static void findMisspellings(TextCheckerClient& client, const UChar* text, int s
tart, int length, Vector<TextCheckingResult>& results) | |
| 74 { | |
| 75 TextBreakIterator* iterator = wordBreakIterator(text + start, length); | |
| 76 if (!iterator) | |
| 77 return; | |
| 78 int wordStart = iterator->current(); | |
| 79 while (0 <= wordStart) { | |
| 80 int wordEnd = iterator->next(); | |
| 81 if (wordEnd < 0) | |
| 82 break; | |
| 83 int wordLength = wordEnd - wordStart; | |
| 84 int misspellingLocation = -1; | |
| 85 int misspellingLength = 0; | |
| 86 client.checkSpellingOfString(String(text + start + wordStart, wordLength
), &misspellingLocation, &misspellingLength); | |
| 87 if (0 < misspellingLength) { | |
| 88 ASSERT(0 <= misspellingLocation && misspellingLocation <= wordLength
); | |
| 89 ASSERT(0 < misspellingLength && misspellingLocation + misspellingLen
gth <= wordLength); | |
| 90 TextCheckingResult misspelling; | |
| 91 misspelling.decoration = TextDecorationTypeSpelling; | |
| 92 misspelling.location = start + wordStart + misspellingLocation; | |
| 93 misspelling.length = misspellingLength; | |
| 94 misspelling.replacement = client.getAutoCorrectSuggestionForMisspell
edWord(String(text + misspelling.location, misspelling.length)); | |
| 95 results.append(misspelling); | |
| 96 } | |
| 97 | |
| 98 wordStart = wordEnd; | |
| 99 } | |
| 100 } | |
| 101 | |
| 102 void expandRangeToSentenceBoundary(Range& range) | |
| 103 { | |
| 104 range.setStart(startOfSentence(VisiblePosition(range.startPosition())).deepE
quivalent()); | |
| 105 range.setEnd(endOfSentence(VisiblePosition(range.endPosition())).deepEquival
ent()); | |
| 106 } | |
| 107 | |
| 108 static PassRefPtrWillBeRawPtr<Range> expandToParagraphBoundary(PassRefPtrWillBeR
awPtr<Range> range) | |
| 109 { | |
| 110 RefPtrWillBeRawPtr<Range> paragraphRange = range->cloneRange(); | |
| 111 paragraphRange->setStart(startOfParagraph(VisiblePosition(range->startPositi
on())).deepEquivalent()); | |
| 112 paragraphRange->setEnd(endOfParagraph(VisiblePosition(range->endPosition()))
.deepEquivalent()); | |
| 113 return paragraphRange; | |
| 114 } | |
| 115 | |
| 116 TextCheckingParagraph::TextCheckingParagraph(PassRefPtrWillBeRawPtr<Range> check
ingRange) | |
| 117 : m_checkingRange(checkingRange) | |
| 118 , m_checkingStart(-1) | |
| 119 , m_checkingEnd(-1) | |
| 120 , m_checkingLength(-1) | |
| 121 { | |
| 122 } | |
| 123 | |
| 124 TextCheckingParagraph::TextCheckingParagraph(PassRefPtrWillBeRawPtr<Range> check
ingRange, PassRefPtrWillBeRawPtr<Range> paragraphRange) | |
| 125 : m_checkingRange(checkingRange) | |
| 126 , m_paragraphRange(paragraphRange) | |
| 127 , m_checkingStart(-1) | |
| 128 , m_checkingEnd(-1) | |
| 129 , m_checkingLength(-1) | |
| 130 { | |
| 131 } | |
| 132 | |
| 133 TextCheckingParagraph::~TextCheckingParagraph() | |
| 134 { | |
| 135 } | |
| 136 | |
| 137 void TextCheckingParagraph::expandRangeToNextEnd() | |
| 138 { | |
| 139 ASSERT(m_checkingRange); | |
| 140 paragraphRange()->setEnd(endOfParagraph(startOfNextParagraph(VisiblePosition
(paragraphRange()->startPosition()))).deepEquivalent()); | |
| 141 invalidateParagraphRangeValues(); | |
| 142 } | |
| 143 | |
| 144 void TextCheckingParagraph::invalidateParagraphRangeValues() | |
| 145 { | |
| 146 m_checkingStart = m_checkingEnd = -1; | |
| 147 m_offsetAsRange = nullptr; | |
| 148 m_text = String(); | |
| 149 } | |
| 150 | |
| 151 int TextCheckingParagraph::rangeLength() const | |
| 152 { | |
| 153 ASSERT(m_checkingRange); | |
| 154 return TextIterator::rangeLength(paragraphRange()->startPosition(), paragrap
hRange()->endPosition()); | |
| 155 } | |
| 156 | |
| 157 PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::paragraphRange() const | |
| 158 { | |
| 159 ASSERT(m_checkingRange); | |
| 160 if (!m_paragraphRange) | |
| 161 m_paragraphRange = expandToParagraphBoundary(checkingRange()); | |
| 162 return m_paragraphRange; | |
| 163 } | |
| 164 | |
| 165 PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::subrange(int characterOffse
t, int characterCount) const | |
| 166 { | |
| 167 ASSERT(m_checkingRange); | |
| 168 EphemeralRange range = calculateCharacterSubrange(EphemeralRange(paragraphRa
nge().get()), characterOffset, characterCount); | |
| 169 if (range.isNull()) | |
| 170 return nullptr; | |
| 171 return Range::create(range.document(), range.startPosition(), range.endPosit
ion()); | |
| 172 } | |
| 173 | |
| 174 int TextCheckingParagraph::offsetTo(const Position& position, ExceptionState& ex
ceptionState) const | |
| 175 { | |
| 176 ASSERT(m_checkingRange); | |
| 177 RefPtrWillBeRawPtr<Range> range = offsetAsRange()->cloneRange(); | |
| 178 range->setEnd(position.computeContainerNode(), position.computeOffsetInConta
inerNode(), exceptionState); | |
| 179 if (exceptionState.hadException()) | |
| 180 return 0; | |
| 181 return TextIterator::rangeLength(range->startPosition(), range->endPosition(
)); | |
| 182 } | |
| 183 | |
| 184 bool TextCheckingParagraph::isEmpty() const | |
| 185 { | |
| 186 // Both predicates should have same result, but we check both just for sure. | |
| 187 // We need to investigate to remove this redundancy. | |
| 188 return isRangeEmpty() || isTextEmpty(); | |
| 189 } | |
| 190 | |
| 191 PassRefPtrWillBeRawPtr<Range> TextCheckingParagraph::offsetAsRange() const | |
| 192 { | |
| 193 ASSERT(m_checkingRange); | |
| 194 if (!m_offsetAsRange) | |
| 195 m_offsetAsRange = Range::create(paragraphRange()->startContainer()->docu
ment(), paragraphRange()->startPosition(), checkingRange()->startPosition()); | |
| 196 | |
| 197 return m_offsetAsRange; | |
| 198 } | |
| 199 | |
| 200 const String& TextCheckingParagraph::text() const | |
| 201 { | |
| 202 ASSERT(m_checkingRange); | |
| 203 if (m_text.isEmpty()) | |
| 204 m_text = plainText(EphemeralRange(paragraphRange().get())); | |
| 205 return m_text; | |
| 206 } | |
| 207 | |
| 208 int TextCheckingParagraph::checkingStart() const | |
| 209 { | |
| 210 ASSERT(m_checkingRange); | |
| 211 if (m_checkingStart == -1) | |
| 212 m_checkingStart = TextIterator::rangeLength(offsetAsRange()->startPositi
on(), offsetAsRange()->endPosition()); | |
| 213 return m_checkingStart; | |
| 214 } | |
| 215 | |
| 216 int TextCheckingParagraph::checkingEnd() const | |
| 217 { | |
| 218 ASSERT(m_checkingRange); | |
| 219 if (m_checkingEnd == -1) | |
| 220 m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRang
e()->startPosition(), checkingRange()->endPosition()); | |
| 221 return m_checkingEnd; | |
| 222 } | |
| 223 | |
| 224 int TextCheckingParagraph::checkingLength() const | |
| 225 { | |
| 226 ASSERT(m_checkingRange); | |
| 227 if (-1 == m_checkingLength) | |
| 228 m_checkingLength = TextIterator::rangeLength(checkingRange()->startPosit
ion(), checkingRange()->endPosition()); | |
| 229 return m_checkingLength; | |
| 230 } | |
| 231 | |
| 232 TextCheckingHelper::TextCheckingHelper(SpellCheckerClient& client, const Positio
n& start, const Position& end) | |
| 233 : m_client(&client) | |
| 234 , m_start(start) | |
| 235 , m_end(end) | |
| 236 { | |
| 237 } | |
| 238 | |
| 239 TextCheckingHelper::~TextCheckingHelper() | |
| 240 { | |
| 241 } | |
| 242 | |
| 243 String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, boo
l markAll, RefPtrWillBeRawPtr<Range>& firstMisspellingRange) | |
| 244 { | |
| 245 WordAwareIterator it(m_start, m_end); | |
| 246 firstMisspellingOffset = 0; | |
| 247 | |
| 248 String firstMisspelling; | |
| 249 int currentChunkOffset = 0; | |
| 250 | |
| 251 while (!it.atEnd()) { | |
| 252 int length = it.length(); | |
| 253 | |
| 254 // Skip some work for one-space-char hunks | |
| 255 if (!(length == 1 && it.characterAt(0) == ' ')) { | |
| 256 | |
| 257 int misspellingLocation = -1; | |
| 258 int misspellingLength = 0; | |
| 259 m_client->textChecker().checkSpellingOfString(it.substring(0, length
), &misspellingLocation, &misspellingLength); | |
| 260 | |
| 261 // 5490627 shows that there was some code path here where the String
constructor below crashes. | |
| 262 // We don't know exactly what combination of bad input caused this,
so we're making this much | |
| 263 // more robust against bad input on release builds. | |
| 264 ASSERT(misspellingLength >= 0); | |
| 265 ASSERT(misspellingLocation >= -1); | |
| 266 ASSERT(!misspellingLength || misspellingLocation >= 0); | |
| 267 ASSERT(misspellingLocation < length); | |
| 268 ASSERT(misspellingLength <= length); | |
| 269 ASSERT(misspellingLocation + misspellingLength <= length); | |
| 270 | |
| 271 if (misspellingLocation >= 0 && misspellingLength > 0 && misspelling
Location < length && misspellingLength <= length && misspellingLocation + misspe
llingLength <= length) { | |
| 272 | |
| 273 // Compute range of misspelled word | |
| 274 const EphemeralRange misspellingRange = calculateCharacterSubran
ge(EphemeralRange(m_start, m_end), currentChunkOffset + misspellingLocation, mis
spellingLength); | |
| 275 | |
| 276 // Remember first-encountered misspelling and its offset. | |
| 277 if (!firstMisspelling) { | |
| 278 firstMisspellingOffset = currentChunkOffset + misspellingLoc
ation; | |
| 279 firstMisspelling = it.substring(misspellingLocation, misspel
lingLength); | |
| 280 firstMisspellingRange = Range::create(misspellingRange.docum
ent(), m_start, m_end); | |
| 281 } | |
| 282 | |
| 283 // Store marker for misspelled word. | |
| 284 misspellingRange.document().markers().addMarker(misspellingRange
.startPosition(), misspellingRange.endPosition(), DocumentMarker::Spelling); | |
| 285 | |
| 286 // Bail out if we're marking only the first misspelling, and not
all instances. | |
| 287 if (!markAll) | |
| 288 break; | |
| 289 } | |
| 290 } | |
| 291 | |
| 292 currentChunkOffset += length; | |
| 293 it.advance(); | |
| 294 } | |
| 295 | |
| 296 return firstMisspelling; | |
| 297 } | |
| 298 | |
| 299 String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b
ool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) | |
| 300 { | |
| 301 if (!unifiedTextCheckerEnabled()) | |
| 302 return ""; | |
| 303 | |
| 304 String firstFoundItem; | |
| 305 String misspelledWord; | |
| 306 String badGrammarPhrase; | |
| 307 | |
| 308 // Initialize out parameters; these will be updated if we find something to
return. | |
| 309 outIsSpelling = true; | |
| 310 outFirstFoundOffset = 0; | |
| 311 outGrammarDetail.location = -1; | |
| 312 outGrammarDetail.length = 0; | |
| 313 outGrammarDetail.guesses.clear(); | |
| 314 outGrammarDetail.userDescription = ""; | |
| 315 | |
| 316 // Expand the search range to encompass entire paragraphs, since text checki
ng needs that much context. | |
| 317 // Determine the character offset from the start of the paragraph to the sta
rt of the original search range, | |
| 318 // since we will want to ignore results in this area. | |
| 319 Position paragraphStart = startOfParagraph(VisiblePosition(m_start)).toParen
tAnchoredPosition(); | |
| 320 Position paragraphEnd = m_end; | |
| 321 int totalRangeLength = TextIterator::rangeLength(paragraphStart, paragraphEn
d); | |
| 322 paragraphEnd = endOfParagraph(VisiblePosition(m_start)).toParentAnchoredPosi
tion(); | |
| 323 | |
| 324 int rangeStartOffset = TextIterator::rangeLength(paragraphStart, m_start); | |
| 325 int totalLengthProcessed = 0; | |
| 326 | |
| 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(paragraphStart, paragraphE
nd); | |
| 332 int currentStartOffset = firstIteration ? rangeStartOffset : 0; | |
| 333 int currentEndOffset = currentLength; | |
| 334 if (inSameParagraph(VisiblePosition(paragraphStart), VisiblePosition(m_e
nd))) { | |
| 335 // Determine the character offset from the end of the original searc
h range to the end of the paragraph, | |
| 336 // since we will want to ignore results in this area. | |
| 337 currentEndOffset = TextIterator::rangeLength(paragraphStart, m_end); | |
| 338 lastIteration = true; | |
| 339 } | |
| 340 if (currentStartOffset < currentEndOffset) { | |
| 341 String paragraphString = plainText(EphemeralRange(paragraphStart, pa
ragraphEnd)); | |
| 342 if (paragraphString.length() > 0) { | |
| 343 bool foundGrammar = false; | |
| 344 int spellingLocation = 0; | |
| 345 int grammarPhraseLocation = 0; | |
| 346 int grammarDetailLocation = 0; | |
| 347 unsigned grammarDetailIndex = 0; | |
| 348 | |
| 349 Vector<TextCheckingResult> results; | |
| 350 TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckin
gTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; | |
| 351 checkTextOfParagraph(m_client->textChecker(), paragraphString, c
heckingTypes, results); | |
| 352 | |
| 353 for (unsigned i = 0; i < results.size(); i++) { | |
| 354 const TextCheckingResult* result = &results[i]; | |
| 355 if (result->decoration == TextDecorationTypeSpelling && resu
lt->location >= currentStartOffset && result->location + result->length <= curre
ntEndOffset) { | |
| 356 ASSERT(result->length > 0 && result->location >= 0); | |
| 357 spellingLocation = result->location; | |
| 358 misspelledWord = paragraphString.substring(result->locat
ion, result->length); | |
| 359 ASSERT(misspelledWord.length()); | |
| 360 break; | |
| 361 } | |
| 362 if (checkGrammar && result->decoration == TextDecorationType
Grammar && result->location < currentEndOffset && result->location + result->len
gth > currentStartOffset) { | |
| 363 ASSERT(result->length > 0 && result->location >= 0); | |
| 364 // We can't stop after the first grammar result, since t
here might still be a spelling result after | |
| 365 // it begins but before the first detail in it, but we c
an stop if we find a second grammar result. | |
| 366 if (foundGrammar) | |
| 367 break; | |
| 368 for (unsigned j = 0; j < result->details.size(); j++) { | |
| 369 const GrammarDetail* detail = &result->details[j]; | |
| 370 ASSERT(detail->length > 0 && detail->location >= 0); | |
| 371 if (result->location + detail->location >= currentSt
artOffset && result->location + detail->location + detail->length <= currentEndO
ffset && (!foundGrammar || result->location + detail->location < grammarDetailLo
cation)) { | |
| 372 grammarDetailIndex = j; | |
| 373 grammarDetailLocation = result->location + detai
l->location; | |
| 374 foundGrammar = true; | |
| 375 } | |
| 376 } | |
| 377 if (foundGrammar) { | |
| 378 grammarPhraseLocation = result->location; | |
| 379 outGrammarDetail = result->details[grammarDetailInde
x]; | |
| 380 badGrammarPhrase = paragraphString.substring(result-
>location, result->length); | |
| 381 ASSERT(badGrammarPhrase.length()); | |
| 382 } | |
| 383 } | |
| 384 } | |
| 385 | |
| 386 if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhr
ase.isEmpty() || spellingLocation <= grammarDetailLocation)) { | |
| 387 int spellingOffset = spellingLocation - currentStartOffset; | |
| 388 if (!firstIteration) | |
| 389 spellingOffset += TextIterator::rangeLength(m_start, par
agraphStart); | |
| 390 outIsSpelling = true; | |
| 391 outFirstFoundOffset = spellingOffset; | |
| 392 firstFoundItem = misspelledWord; | |
| 393 break; | |
| 394 } | |
| 395 if (checkGrammar && !badGrammarPhrase.isEmpty()) { | |
| 396 int grammarPhraseOffset = grammarPhraseLocation - currentSta
rtOffset; | |
| 397 if (!firstIteration) | |
| 398 grammarPhraseOffset += TextIterator::rangeLength(m_start
, paragraphStart); | |
| 399 outIsSpelling = false; | |
| 400 outFirstFoundOffset = grammarPhraseOffset; | |
| 401 firstFoundItem = badGrammarPhrase; | |
| 402 break; | |
| 403 } | |
| 404 } | |
| 405 } | |
| 406 if (lastIteration || totalLengthProcessed + currentLength >= totalRangeL
ength) | |
| 407 break; | |
| 408 VisiblePosition newParagraphStart = startOfNextParagraph(VisiblePosition
(paragraphEnd)); | |
| 409 paragraphStart = newParagraphStart.toParentAnchoredPosition(); | |
| 410 paragraphEnd = endOfParagraph(newParagraphStart).toParentAnchoredPositio
n(); | |
| 411 firstIteration = false; | |
| 412 totalLengthProcessed += currentLength; | |
| 413 } | |
| 414 return firstFoundItem; | |
| 415 } | |
| 416 | |
| 417 int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& gram
marDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool m
arkAll) const | |
| 418 { | |
| 419 // Found some bad grammar. Find the earliest detail range that starts in our
search range (if any). | |
| 420 // Optionally add a DocumentMarker for each detail in the range. | |
| 421 int earliestDetailLocationSoFar = -1; | |
| 422 int earliestDetailIndex = -1; | |
| 423 for (unsigned i = 0; i < grammarDetails.size(); i++) { | |
| 424 const GrammarDetail* detail = &grammarDetails[i]; | |
| 425 ASSERT(detail->length > 0 && detail->location >= 0); | |
| 426 | |
| 427 int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->lo
cation; | |
| 428 | |
| 429 // Skip this detail if it starts before the original search range | |
| 430 if (detailStartOffsetInParagraph < startOffset) | |
| 431 continue; | |
| 432 | |
| 433 // Skip this detail if it starts after the original search range | |
| 434 if (detailStartOffsetInParagraph >= endOffset) | |
| 435 continue; | |
| 436 | |
| 437 if (markAll) { | |
| 438 const EphemeralRange badGrammarRange = calculateCharacterSubrange(Ep
hemeralRange(m_start, m_end), badGrammarPhraseLocation - startOffset + detail->l
ocation, detail->length); | |
| 439 badGrammarRange.document().markers().addMarker(badGrammarRange.start
Position(), badGrammarRange.endPosition(), DocumentMarker::Grammar, detail->user
Description); | |
| 440 } | |
| 441 | |
| 442 // Remember this detail only if it's earlier than our current candidate
(the details aren't in a guaranteed order) | |
| 443 if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->loc
ation) { | |
| 444 earliestDetailIndex = i; | |
| 445 earliestDetailLocationSoFar = detail->location; | |
| 446 } | |
| 447 } | |
| 448 | |
| 449 return earliestDetailIndex; | |
| 450 } | |
| 451 | |
| 452 String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail,
int& outGrammarPhraseOffset, bool markAll) | |
| 453 { | |
| 454 // Initialize out parameters; these will be updated if we find something to
return. | |
| 455 outGrammarDetail.location = -1; | |
| 456 outGrammarDetail.length = 0; | |
| 457 outGrammarDetail.guesses.clear(); | |
| 458 outGrammarDetail.userDescription = ""; | |
| 459 outGrammarPhraseOffset = 0; | |
| 460 | |
| 461 String firstBadGrammarPhrase; | |
| 462 | |
| 463 // Expand the search range to encompass entire paragraphs, since grammar che
cking needs that much context. | |
| 464 // Determine the character offset from the start of the paragraph to the sta
rt of the original search range, | |
| 465 // since we will want to ignore results in this area. | |
| 466 TextCheckingParagraph paragraph(Range::create(m_start.computeContainerNode()
->document(), m_start, m_end)); | |
| 467 | |
| 468 // Start checking from beginning of paragraph, but skip past results that oc
cur before the start of the original search range. | |
| 469 int startOffset = 0; | |
| 470 while (startOffset < paragraph.checkingEnd()) { | |
| 471 Vector<GrammarDetail> grammarDetails; | |
| 472 int badGrammarPhraseLocation = -1; | |
| 473 int badGrammarPhraseLength = 0; | |
| 474 m_client->textChecker().checkGrammarOfString(paragraph.textSubstring(sta
rtOffset), grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength); | |
| 475 | |
| 476 if (!badGrammarPhraseLength) { | |
| 477 ASSERT(badGrammarPhraseLocation == -1); | |
| 478 return String(); | |
| 479 } | |
| 480 | |
| 481 ASSERT(badGrammarPhraseLocation >= 0); | |
| 482 badGrammarPhraseLocation += startOffset; | |
| 483 | |
| 484 | |
| 485 // Found some bad grammar. Find the earliest detail range that starts in
our search range (if any). | |
| 486 int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarP
hraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll); | |
| 487 if (badGrammarIndex >= 0) { | |
| 488 ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size(
)); | |
| 489 outGrammarDetail = grammarDetails[badGrammarIndex]; | |
| 490 } | |
| 491 | |
| 492 // If we found a detail in range, then we have found the first bad phras
e (unless we found one earlier but | |
| 493 // kept going so we could mark all instances). | |
| 494 if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) { | |
| 495 outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checki
ngStart(); | |
| 496 firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLoca
tion, badGrammarPhraseLength); | |
| 497 | |
| 498 // Found one. We're done now, unless we're marking each instance. | |
| 499 if (!markAll) | |
| 500 break; | |
| 501 } | |
| 502 | |
| 503 // These results were all between the start of the paragraph and the sta
rt of the search range; look | |
| 504 // beyond this phrase. | |
| 505 startOffset = badGrammarPhraseLocation + badGrammarPhraseLength; | |
| 506 } | |
| 507 | |
| 508 return firstBadGrammarPhrase; | |
| 509 } | |
| 510 | |
| 511 void TextCheckingHelper::markAllMisspellings(RefPtrWillBeRawPtr<Range>& firstMis
spellingRange) | |
| 512 { | |
| 513 // Use the "markAll" feature of findFirstMisspelling. Ignore the return valu
e and the "out parameter"; | |
| 514 // all we need to do is mark every instance. | |
| 515 int ignoredOffset; | |
| 516 findFirstMisspelling(ignoredOffset, true, firstMisspellingRange); | |
| 517 } | |
| 518 | |
| 519 void TextCheckingHelper::markAllBadGrammar() | |
| 520 { | |
| 521 // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return valu
e and "out parameters"; all we need to | |
| 522 // do is mark every instance. | |
| 523 GrammarDetail ignoredGrammarDetail; | |
| 524 int ignoredOffset; | |
| 525 findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true); | |
| 526 } | |
| 527 | |
| 528 bool TextCheckingHelper::unifiedTextCheckerEnabled() const | |
| 529 { | |
| 530 ASSERT(m_start.isNotNull()); | |
| 531 Document& doc = m_start.computeContainerNode()->document(); | |
| 532 return blink::unifiedTextCheckerEnabled(doc.frame()); | |
| 533 } | |
| 534 | |
| 535 void checkTextOfParagraph(TextCheckerClient& client, const String& text, TextChe
ckingTypeMask checkingTypes, Vector<TextCheckingResult>& results) | |
| 536 { | |
| 537 Vector<UChar> characters; | |
| 538 text.appendTo(characters); | |
| 539 unsigned length = text.length(); | |
| 540 | |
| 541 Vector<TextCheckingResult> spellingResult; | |
| 542 if (checkingTypes & TextCheckingTypeSpelling) | |
| 543 findMisspellings(client, characters.data(), 0, length, spellingResult); | |
| 544 | |
| 545 Vector<TextCheckingResult> grammarResult; | |
| 546 if (checkingTypes & TextCheckingTypeGrammar) { | |
| 547 // Only checks grammartical error before the first misspellings | |
| 548 int grammarCheckLength = length; | |
| 549 for (const auto& spelling : spellingResult) { | |
| 550 if (spelling.location < grammarCheckLength) | |
| 551 grammarCheckLength = spelling.location; | |
| 552 } | |
| 553 | |
| 554 findBadGrammars(client, characters.data(), 0, grammarCheckLength, gramma
rResult); | |
| 555 } | |
| 556 | |
| 557 if (grammarResult.size()) | |
| 558 results.swap(grammarResult); | |
| 559 | |
| 560 if (spellingResult.size()) { | |
| 561 if (results.isEmpty()) | |
| 562 results.swap(spellingResult); | |
| 563 else | |
| 564 results.appendVector(spellingResult); | |
| 565 } | |
| 566 } | |
| 567 | |
| 568 bool unifiedTextCheckerEnabled(const LocalFrame* frame) | |
| 569 { | |
| 570 if (!frame) | |
| 571 return false; | |
| 572 | |
| 573 const Settings* settings = frame->settings(); | |
| 574 if (!settings) | |
| 575 return false; | |
| 576 | |
| 577 return settings->unifiedTextCheckerEnabled(); | |
| 578 } | |
| 579 | |
| 580 } | |
| OLD | NEW |