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 |