| 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 "core/editing/spellcheck/TextCheckingHelper.h" | |
| 28 | |
| 29 #include "core/dom/Document.h" | |
| 30 #include "core/dom/Range.h" | |
| 31 #include "core/editing/VisiblePosition.h" | |
| 32 #include "core/editing/VisibleUnits.h" | |
| 33 #include "core/editing/iterators/CharacterIterator.h" | |
| 34 #include "core/editing/iterators/WordAwareIterator.h" | |
| 35 #include "core/editing/markers/DocumentMarkerController.h" | |
| 36 #include "core/frame/LocalFrame.h" | |
| 37 #include "core/frame/Settings.h" | |
| 38 #include "core/page/SpellCheckerClient.h" | |
| 39 #include "platform/text/TextBreakIterator.h" | |
| 40 #include "platform/text/TextCheckerClient.h" | |
| 41 | |
| 42 namespace blink { | |
| 43 | |
| 44 static void findMisspellings(TextCheckerClient& client, const String& text, Vect
or<TextCheckingResult>& results) | |
| 45 { | |
| 46 Vector<UChar> characters; | |
| 47 text.appendTo(characters); | |
| 48 unsigned length = text.length(); | |
| 49 | |
| 50 TextBreakIterator* iterator = wordBreakIterator(characters.data(), length); | |
| 51 if (!iterator) | |
| 52 return; | |
| 53 | |
| 54 int wordStart = iterator->current(); | |
| 55 while (0 <= wordStart) { | |
| 56 int wordEnd = iterator->next(); | |
| 57 if (wordEnd < 0) | |
| 58 break; | |
| 59 int wordLength = wordEnd - wordStart; | |
| 60 int misspellingLocation = -1; | |
| 61 int misspellingLength = 0; | |
| 62 client.checkSpellingOfString(String(characters.data() + wordStart, wordL
ength), &misspellingLocation, &misspellingLength); | |
| 63 if (0 < misspellingLength) { | |
| 64 DCHECK_LE(0, misspellingLocation); | |
| 65 DCHECK_LE(misspellingLocation, wordLength); | |
| 66 DCHECK_LT(0, misspellingLength); | |
| 67 DCHECK_LE(misspellingLocation + misspellingLength, wordLength); | |
| 68 TextCheckingResult misspelling; | |
| 69 misspelling.decoration = TextDecorationTypeSpelling; | |
| 70 misspelling.location = wordStart + misspellingLocation; | |
| 71 misspelling.length = misspellingLength; | |
| 72 results.append(misspelling); | |
| 73 } | |
| 74 | |
| 75 wordStart = wordEnd; | |
| 76 } | |
| 77 } | |
| 78 | |
| 79 static EphemeralRange expandToParagraphBoundary(const EphemeralRange& range) | |
| 80 { | |
| 81 const VisiblePosition& start = createVisiblePosition(range.startPosition()); | |
| 82 DCHECK(start.isNotNull()) << range.startPosition(); | |
| 83 const VisiblePosition& paragraphStart = startOfParagraph(start); | |
| 84 DCHECK(paragraphStart.isNotNull()) << range.startPosition(); | |
| 85 | |
| 86 const VisiblePosition& end = createVisiblePosition(range.endPosition()); | |
| 87 DCHECK(end.isNotNull()) << range.endPosition(); | |
| 88 const VisiblePosition& paragraphEnd = endOfParagraph(end); | |
| 89 DCHECK(paragraphEnd.isNotNull()) << range.endPosition(); | |
| 90 | |
| 91 return EphemeralRange(paragraphStart.deepEquivalent(), paragraphEnd.deepEqui
valent()); | |
| 92 } | |
| 93 | |
| 94 TextCheckingParagraph::TextCheckingParagraph(const EphemeralRange& checkingRange
) | |
| 95 : m_checkingRange(checkingRange) | |
| 96 , m_checkingStart(-1) | |
| 97 , m_checkingEnd(-1) | |
| 98 , m_checkingLength(-1) | |
| 99 { | |
| 100 } | |
| 101 | |
| 102 TextCheckingParagraph::TextCheckingParagraph(const EphemeralRange& checkingRange
, const EphemeralRange& paragraphRange) | |
| 103 : m_checkingRange(checkingRange) | |
| 104 , m_paragraphRange(paragraphRange) | |
| 105 , m_checkingStart(-1) | |
| 106 , m_checkingEnd(-1) | |
| 107 , m_checkingLength(-1) | |
| 108 { | |
| 109 } | |
| 110 | |
| 111 TextCheckingParagraph::TextCheckingParagraph(Range* checkingRange, Range* paragr
aphRange) | |
| 112 : m_checkingRange(checkingRange) | |
| 113 , m_paragraphRange(paragraphRange) | |
| 114 , m_checkingStart(-1) | |
| 115 , m_checkingEnd(-1) | |
| 116 , m_checkingLength(-1) | |
| 117 { | |
| 118 } | |
| 119 | |
| 120 TextCheckingParagraph::~TextCheckingParagraph() | |
| 121 { | |
| 122 } | |
| 123 | |
| 124 void TextCheckingParagraph::expandRangeToNextEnd() | |
| 125 { | |
| 126 DCHECK(m_checkingRange.isNotNull()); | |
| 127 setParagraphRange(EphemeralRange(paragraphRange().startPosition(), endOfPara
graph(startOfNextParagraph(createVisiblePosition(paragraphRange().startPosition(
)))).deepEquivalent())); | |
| 128 invalidateParagraphRangeValues(); | |
| 129 } | |
| 130 | |
| 131 void TextCheckingParagraph::invalidateParagraphRangeValues() | |
| 132 { | |
| 133 m_checkingStart = m_checkingEnd = -1; | |
| 134 m_offsetAsRange = EphemeralRange(); | |
| 135 m_text = String(); | |
| 136 } | |
| 137 | |
| 138 int TextCheckingParagraph::rangeLength() const | |
| 139 { | |
| 140 DCHECK(m_checkingRange.isNotNull()); | |
| 141 return TextIterator::rangeLength(paragraphRange().startPosition(), paragraph
Range().endPosition()); | |
| 142 } | |
| 143 | |
| 144 EphemeralRange TextCheckingParagraph::paragraphRange() const | |
| 145 { | |
| 146 DCHECK(m_checkingRange.isNotNull()); | |
| 147 if (m_paragraphRange.isNull()) | |
| 148 m_paragraphRange = expandToParagraphBoundary(checkingRange()); | |
| 149 return m_paragraphRange; | |
| 150 } | |
| 151 | |
| 152 void TextCheckingParagraph::setParagraphRange(const EphemeralRange& range) | |
| 153 { | |
| 154 m_paragraphRange = range; | |
| 155 } | |
| 156 | |
| 157 EphemeralRange TextCheckingParagraph::subrange(int characterOffset, int characte
rCount) const | |
| 158 { | |
| 159 DCHECK(m_checkingRange.isNotNull()); | |
| 160 return calculateCharacterSubrange(paragraphRange(), characterOffset, charact
erCount); | |
| 161 } | |
| 162 | |
| 163 int TextCheckingParagraph::offsetTo(const Position& position) const | |
| 164 { | |
| 165 DCHECK(m_checkingRange.isNotNull()); | |
| 166 return TextIterator::rangeLength(offsetAsRange().startPosition(), position); | |
| 167 } | |
| 168 | |
| 169 bool TextCheckingParagraph::isEmpty() const | |
| 170 { | |
| 171 // Both predicates should have same result, but we check both just to be sur
e. | |
| 172 // We need to investigate to remove this redundancy. | |
| 173 return isRangeEmpty() || isTextEmpty(); | |
| 174 } | |
| 175 | |
| 176 EphemeralRange TextCheckingParagraph::offsetAsRange() const | |
| 177 { | |
| 178 DCHECK(m_checkingRange.isNotNull()); | |
| 179 if (m_offsetAsRange.isNotNull()) | |
| 180 return m_offsetAsRange; | |
| 181 const Position& paragraphStart = paragraphRange().startPosition(); | |
| 182 const Position& checkingStart = checkingRange().startPosition(); | |
| 183 if (paragraphStart <= checkingStart) { | |
| 184 m_offsetAsRange = EphemeralRange(paragraphStart, checkingStart); | |
| 185 return m_offsetAsRange; | |
| 186 } | |
| 187 // editing/pasteboard/paste-table-001.html and more reach here. | |
| 188 m_offsetAsRange = EphemeralRange(checkingStart, paragraphStart); | |
| 189 return m_offsetAsRange; | |
| 190 } | |
| 191 | |
| 192 const String& TextCheckingParagraph::text() const | |
| 193 { | |
| 194 DCHECK(m_checkingRange.isNotNull()); | |
| 195 if (m_text.isEmpty()) | |
| 196 m_text = plainText(paragraphRange()); | |
| 197 return m_text; | |
| 198 } | |
| 199 | |
| 200 int TextCheckingParagraph::checkingStart() const | |
| 201 { | |
| 202 DCHECK(m_checkingRange.isNotNull()); | |
| 203 if (m_checkingStart == -1) | |
| 204 m_checkingStart = TextIterator::rangeLength(offsetAsRange().startPositio
n(), offsetAsRange().endPosition()); | |
| 205 return m_checkingStart; | |
| 206 } | |
| 207 | |
| 208 int TextCheckingParagraph::checkingEnd() const | |
| 209 { | |
| 210 DCHECK(m_checkingRange.isNotNull()); | |
| 211 if (m_checkingEnd == -1) | |
| 212 m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRang
e().startPosition(), checkingRange().endPosition()); | |
| 213 return m_checkingEnd; | |
| 214 } | |
| 215 | |
| 216 int TextCheckingParagraph::checkingLength() const | |
| 217 { | |
| 218 DCHECK(m_checkingRange.isNotNull()); | |
| 219 if (-1 == m_checkingLength) | |
| 220 m_checkingLength = TextIterator::rangeLength(checkingRange().startPositi
on(), checkingRange().endPosition()); | |
| 221 return m_checkingLength; | |
| 222 } | |
| 223 | |
| 224 TextCheckingHelper::TextCheckingHelper(SpellCheckerClient& client, const Positio
n& start, const Position& end) | |
| 225 : m_client(&client) | |
| 226 , m_start(start) | |
| 227 , m_end(end) | |
| 228 { | |
| 229 } | |
| 230 | |
| 231 TextCheckingHelper::~TextCheckingHelper() | |
| 232 { | |
| 233 } | |
| 234 | |
| 235 String TextCheckingHelper::findFirstMisspellingOrBadGrammar(int& outFirstFoundOf
fset) | |
| 236 { | |
| 237 String firstFoundItem; | |
| 238 String misspelledWord; | |
| 239 | |
| 240 // Initialize out parameter; it will be updated if we find something to retu
rn. | |
| 241 outFirstFoundOffset = 0; | |
| 242 | |
| 243 // Expand the search range to encompass entire paragraphs, since text checki
ng needs that much context. | |
| 244 // Determine the character offset from the start of the paragraph to the sta
rt of the original search range, | |
| 245 // since we will want to ignore results in this area. | |
| 246 Position paragraphStart = startOfParagraph(createVisiblePosition(m_start)).t
oParentAnchoredPosition(); | |
| 247 Position paragraphEnd = m_end; | |
| 248 int totalRangeLength = TextIterator::rangeLength(paragraphStart, paragraphEn
d); | |
| 249 paragraphEnd = endOfParagraph(createVisiblePosition(m_start)).toParentAnchor
edPosition(); | |
| 250 | |
| 251 int rangeStartOffset = TextIterator::rangeLength(paragraphStart, m_start); | |
| 252 int totalLengthProcessed = 0; | |
| 253 | |
| 254 bool firstIteration = true; | |
| 255 bool lastIteration = false; | |
| 256 while (totalLengthProcessed < totalRangeLength) { | |
| 257 // Iterate through the search range by paragraphs, checking each one for
spelling. | |
| 258 int currentLength = TextIterator::rangeLength(paragraphStart, paragraphE
nd); | |
| 259 int currentStartOffset = firstIteration ? rangeStartOffset : 0; | |
| 260 int currentEndOffset = currentLength; | |
| 261 if (inSameParagraph(createVisiblePosition(paragraphStart), createVisible
Position(m_end))) { | |
| 262 // Determine the character offset from the end of the original searc
h range to the end of the paragraph, | |
| 263 // since we will want to ignore results in this area. | |
| 264 currentEndOffset = TextIterator::rangeLength(paragraphStart, m_end); | |
| 265 lastIteration = true; | |
| 266 } | |
| 267 if (currentStartOffset < currentEndOffset) { | |
| 268 String paragraphString = plainText(EphemeralRange(paragraphStart, pa
ragraphEnd)); | |
| 269 if (paragraphString.length() > 0) { | |
| 270 int spellingLocation = 0; | |
| 271 | |
| 272 Vector<TextCheckingResult> results; | |
| 273 findMisspellings(m_client->textChecker(), paragraphString, resul
ts); | |
| 274 | |
| 275 for (unsigned i = 0; i < results.size(); i++) { | |
| 276 const TextCheckingResult* result = &results[i]; | |
| 277 if (result->decoration == TextDecorationTypeSpelling && resu
lt->location >= currentStartOffset && result->location + result->length <= curre
ntEndOffset) { | |
| 278 DCHECK_GT(result->length, 0); | |
| 279 DCHECK_GE(result->location, 0); | |
| 280 spellingLocation = result->location; | |
| 281 misspelledWord = paragraphString.substring(result->locat
ion, result->length); | |
| 282 DCHECK(misspelledWord.length()); | |
| 283 break; | |
| 284 } | |
| 285 } | |
| 286 | |
| 287 if (!misspelledWord.isEmpty()) { | |
| 288 int spellingOffset = spellingLocation - currentStartOffset; | |
| 289 if (!firstIteration) | |
| 290 spellingOffset += TextIterator::rangeLength(m_start, par
agraphStart); | |
| 291 outFirstFoundOffset = spellingOffset; | |
| 292 firstFoundItem = misspelledWord; | |
| 293 break; | |
| 294 } | |
| 295 } | |
| 296 } | |
| 297 if (lastIteration || totalLengthProcessed + currentLength >= totalRangeL
ength) | |
| 298 break; | |
| 299 VisiblePosition newParagraphStart = startOfNextParagraph(createVisiblePo
sition(paragraphEnd)); | |
| 300 paragraphStart = newParagraphStart.toParentAnchoredPosition(); | |
| 301 paragraphEnd = endOfParagraph(newParagraphStart).toParentAnchoredPositio
n(); | |
| 302 firstIteration = false; | |
| 303 totalLengthProcessed += currentLength; | |
| 304 } | |
| 305 return firstFoundItem; | |
| 306 } | |
| 307 | |
| 308 } // namespace blink | |
| OLD | NEW |