| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/history/query_parser.h" | 5 #include "chrome/browser/history/query_parser.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/compiler_specific.h" | 9 #include "base/compiler_specific.h" |
| 10 #include "base/i18n/break_iterator.h" | 10 #include "base/i18n/break_iterator.h" |
| 11 #include "base/i18n/case_conversion.h" | 11 #include "base/i18n/case_conversion.h" |
| 12 #include "base/logging.h" | 12 #include "base/logging.h" |
| 13 #include "base/stl_util.h" | 13 #include "base/stl_util.h" |
| 14 #include "base/string_util.h" | |
| 15 | 14 |
| 16 namespace { | 15 namespace { |
| 17 | 16 |
| 18 // Returns true if |mp1.first| is less than |mp2.first|. This is used to | 17 // Returns true if |mp1.first| is less than |mp2.first|. This is used to |
| 19 // sort match positions. | 18 // sort match positions. |
| 20 int CompareMatchPosition(const Snippet::MatchPosition& mp1, | 19 int CompareMatchPosition(const Snippet::MatchPosition& mp1, |
| 21 const Snippet::MatchPosition& mp2) { | 20 const Snippet::MatchPosition& mp2) { |
| 22 return mp1.first < mp2.first; | 21 return mp1.first < mp2.first; |
| 23 } | 22 } |
| 24 | 23 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 40 mp.second = i->second; | 39 mp.second = i->second; |
| 41 i = matches->erase(i); | 40 i = matches->erase(i); |
| 42 } else { | 41 } else { |
| 43 return; | 42 return; |
| 44 } | 43 } |
| 45 } | 44 } |
| 46 } | 45 } |
| 47 | 46 |
| 48 // Sorts the match positions in |matches| by their first index, then coalesces | 47 // Sorts the match positions in |matches| by their first index, then coalesces |
| 49 // any match positions that intersect each other. | 48 // any match positions that intersect each other. |
| 50 void CoalesceAndSortMatchPositions(Snippet::MatchPositions* matches) { | 49 void CoalseAndSortMatchPositions(Snippet::MatchPositions* matches) { |
| 51 std::sort(matches->begin(), matches->end(), &CompareMatchPosition); | 50 std::sort(matches->begin(), matches->end(), &CompareMatchPosition); |
| 52 // WARNING: we don't use iterator here as CoalesceMatchesFrom may remove | 51 // WARNING: we don't use iterator here as CoalesceMatchesFrom may remove |
| 53 // from matches. | 52 // from matches. |
| 54 for (size_t i = 0; i < matches->size(); ++i) | 53 for (size_t i = 0; i < matches->size(); ++i) |
| 55 CoalesceMatchesFrom(i, matches); | 54 CoalesceMatchesFrom(i, matches); |
| 56 } | 55 } |
| 57 | 56 |
| 58 // Returns true if the character is considered a quote. | 57 // Returns true if the character is considered a quote. |
| 59 bool IsQueryQuote(wchar_t ch) { | 58 bool IsQueryQuote(wchar_t ch) { |
| 60 return ch == '"' || | 59 return ch == '"' || |
| 61 ch == 0xab || // left pointing double angle bracket | 60 ch == 0xab || // left pointing double angle bracket |
| 62 ch == 0xbb || // right pointing double angle bracket | 61 ch == 0xbb || // right pointing double angle bracket |
| 63 ch == 0x201c || // left double quotation mark | 62 ch == 0x201c || // left double quotation mark |
| 64 ch == 0x201d || // right double quotation mark | 63 ch == 0x201d || // right double quotation mark |
| 65 ch == 0x201e; // double low-9 quotation mark | 64 ch == 0x201e; // double low-9 quotation mark |
| 66 } | 65 } |
| 67 | 66 |
| 68 // Returns true if the character is considered non-breaking when it appears in | |
| 69 // the middle of a word. This can be used to prevent an URL-like query from | |
| 70 // being broken into multiple words. | |
| 71 bool IsNonBreakingSymbol(wchar_t ch) { | |
| 72 return ch == '.' || ch == '-'; | |
| 73 } | |
| 74 | |
| 75 } // namespace | 67 } // namespace |
| 76 | 68 |
| 77 // Inheritance structure: | 69 // Inheritance structure: |
| 78 // Queries are represented as trees of QueryNodes. | 70 // Queries are represented as trees of QueryNodes. |
| 79 // QueryNodes are either a collection of subnodes (a QueryNodeList) | 71 // QueryNodes are either a collection of subnodes (a QueryNodeList) |
| 80 // or a single word (a QueryNodeWord). | 72 // or a single word (a QueryNodeWord). |
| 81 | 73 |
| 82 // A QueryNodeWord is a sequence of consecutive characters in a query. | 74 // A QueryNodeWord is a single word in the query. |
| 83 // It can be an actual word or an URL-like sequence of characters. | |
| 84 class QueryNodeWord : public QueryNode { | 75 class QueryNodeWord : public QueryNode { |
| 85 public: | 76 public: |
| 86 explicit QueryNodeWord(const string16& word); | 77 explicit QueryNodeWord(const string16& word); |
| 87 virtual ~QueryNodeWord(); | 78 virtual ~QueryNodeWord(); |
| 88 | 79 |
| 89 const string16& word() const { return word_; } | 80 const string16& word() const { return word_; } |
| 90 | 81 |
| 91 void set_literal(bool literal) { literal_ = literal; } | 82 void set_literal(bool literal) { literal_ = literal; } |
| 92 void Append(const string16& word); | |
| 93 | 83 |
| 94 // QueryNode: | 84 // QueryNode: |
| 95 virtual int AppendToSQLiteQuery(string16* query) const OVERRIDE; | 85 virtual int AppendToSQLiteQuery(string16* query) const OVERRIDE; |
| 96 virtual bool IsWord() const OVERRIDE; | 86 virtual bool IsWord() const OVERRIDE; |
| 97 virtual bool Matches(const string16& word, bool exact) const OVERRIDE; | 87 virtual bool Matches(const string16& word, bool exact) const OVERRIDE; |
| 98 virtual bool HasMatchIn( | 88 virtual bool HasMatchIn( |
| 99 const std::vector<QueryWord>& words, | 89 const std::vector<QueryWord>& words, |
| 100 Snippet::MatchPositions* match_positions) const OVERRIDE; | 90 Snippet::MatchPositions* match_positions) const OVERRIDE; |
| 101 virtual void AppendWords(std::vector<string16>* words) const OVERRIDE; | 91 virtual void AppendWords(std::vector<string16>* words) const OVERRIDE; |
| 102 | 92 |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 144 return true; | 134 return true; |
| 145 } | 135 } |
| 146 } | 136 } |
| 147 return false; | 137 return false; |
| 148 } | 138 } |
| 149 | 139 |
| 150 void QueryNodeWord::AppendWords(std::vector<string16>* words) const { | 140 void QueryNodeWord::AppendWords(std::vector<string16>* words) const { |
| 151 words->push_back(word_); | 141 words->push_back(word_); |
| 152 } | 142 } |
| 153 | 143 |
| 154 void QueryNodeWord::Append(const string16& str) { | |
| 155 word_ += str; | |
| 156 } | |
| 157 | |
| 158 // A QueryNodeList has a collection of QueryNodes which are deleted in the end. | 144 // A QueryNodeList has a collection of QueryNodes which are deleted in the end. |
| 159 class QueryNodeList : public QueryNode { | 145 class QueryNodeList : public QueryNode { |
| 160 public: | 146 public: |
| 161 typedef std::vector<QueryNode*> QueryNodeVector; | 147 typedef std::vector<QueryNode*> QueryNodeVector; |
| 162 | 148 |
| 163 QueryNodeList(); | 149 QueryNodeList(); |
| 164 virtual ~QueryNodeList(); | 150 virtual ~QueryNodeList(); |
| 165 | 151 |
| 166 QueryNodeVector* children() { return &children_; } | 152 QueryNodeVector* children() { return &children_; } |
| 167 | 153 |
| (...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 354 if (!query_nodes[i]->HasMatchIn(query_words, &matches)) | 340 if (!query_nodes[i]->HasMatchIn(query_words, &matches)) |
| 355 return false; | 341 return false; |
| 356 } | 342 } |
| 357 if (lower_text.length() != text.length()) { | 343 if (lower_text.length() != text.length()) { |
| 358 // The lower case string differs from the original string. The matches are | 344 // The lower case string differs from the original string. The matches are |
| 359 // meaningless. | 345 // meaningless. |
| 360 // TODO(sky): we need a better way to align the positions so that we don't | 346 // TODO(sky): we need a better way to align the positions so that we don't |
| 361 // completely punt here. | 347 // completely punt here. |
| 362 match_positions->clear(); | 348 match_positions->clear(); |
| 363 } else { | 349 } else { |
| 364 CoalesceAndSortMatchPositions(&matches); | 350 CoalseAndSortMatchPositions(&matches); |
| 365 match_positions->swap(matches); | 351 match_positions->swap(matches); |
| 366 } | 352 } |
| 367 return true; | 353 return true; |
| 368 } | 354 } |
| 369 | 355 |
| 370 bool QueryParser::ParseQueryImpl(const string16& query, QueryNodeList* root) { | 356 bool QueryParser::ParseQueryImpl(const string16& query, QueryNodeList* root) { |
| 371 base::i18n::BreakIterator iter(query, base::i18n::BreakIterator::BREAK_WORD); | 357 base::i18n::BreakIterator iter(query, base::i18n::BreakIterator::BREAK_WORD); |
| 372 // TODO(evanm): support a locale here | 358 // TODO(evanm): support a locale here |
| 373 if (!iter.Init()) | 359 if (!iter.Init()) |
| 374 return false; | 360 return false; |
| 375 | 361 |
| 376 // To handle nesting, we maintain a stack of QueryNodeLists. | 362 // To handle nesting, we maintain a stack of QueryNodeLists. |
| 377 // The last element (back) of the stack contains the current, deepest node. | 363 // The last element (back) of the stack contains the current, deepest node. |
| 378 std::vector<QueryNodeList*> query_stack; | 364 std::vector<QueryNodeList*> query_stack; |
| 379 query_stack.push_back(root); | 365 query_stack.push_back(root); |
| 380 | 366 |
| 381 bool in_quotes = false; // whether we're currently in a quoted phrase | 367 bool in_quotes = false; // whether we're currently in a quoted phrase |
| 382 QueryNodeWord* current_word = NULL; | |
| 383 while (iter.Advance()) { | 368 while (iter.Advance()) { |
| 384 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It | 369 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It |
| 385 // is not necessarily a word, it could also be a punctuation or whitespace | 370 // is not necessarily a word, but could also be a sequence of punctuation |
| 386 // character. Punctuation is preserved inside quotes, and otherwise removed | 371 // or whitespace. |
| 387 // except if it is a non-breaking character in the middle of a word. | 372 if (iter.IsWord()) { |
| 388 | 373 QueryNodeWord* word_node = new QueryNodeWord(iter.GetString()); |
| 389 wchar_t last_char = query[iter.prev()]; | 374 if (in_quotes) |
| 390 if (IsQueryQuote(last_char)) { | 375 word_node->set_literal(true); |
| 391 if (!in_quotes) { | 376 query_stack.back()->AddChild(word_node); |
| 392 QueryNodeList* quotes_node = new QueryNodePhrase; | 377 } else { // Punctuation. |
| 393 query_stack.back()->AddChild(quotes_node); | 378 if (IsQueryQuote(query[iter.prev()])) { |
| 394 query_stack.push_back(quotes_node); | 379 if (!in_quotes) { |
| 395 in_quotes = true; | 380 QueryNodeList* quotes_node = new QueryNodePhrase; |
| 396 } else { | 381 query_stack.back()->AddChild(quotes_node); |
| 397 query_stack.pop_back(); // Stop adding to the quoted phrase. | 382 query_stack.push_back(quotes_node); |
| 398 in_quotes = false; | 383 in_quotes = true; |
| 384 } else { |
| 385 query_stack.pop_back(); // Stop adding to the quoted phrase. |
| 386 in_quotes = false; |
| 387 } |
| 399 } | 388 } |
| 400 current_word = NULL; | |
| 401 } else if (iter.IsWord() || (in_quotes && !IsWhitespace(last_char))) { | |
| 402 // Append to the current word if the new token is a word or a non- | |
| 403 // whitespace character inside quotes. | |
| 404 if (current_word) { | |
| 405 current_word->Append(iter.GetString()); | |
| 406 } else { | |
| 407 current_word = new QueryNodeWord(iter.GetString()); | |
| 408 current_word->set_literal(in_quotes); | |
| 409 query_stack.back()->AddChild(current_word); | |
| 410 } | |
| 411 } else if (current_word != NULL) { | |
| 412 // Allow non-breaking symbols inside a word. | |
| 413 // Any other punctuation or whitespace character ends the current word. | |
| 414 | |
| 415 // TODO(dubroy): Consider sharing code with the omnibox to allow a more | |
| 416 // accurate "best guess" of whether a sequence of characters is a URL. | |
| 417 if (IsNonBreakingSymbol(last_char)) | |
| 418 current_word->Append(iter.GetString()); | |
| 419 else | |
| 420 current_word = NULL; | |
| 421 } | 389 } |
| 422 } | 390 } |
| 423 | 391 |
| 424 root->RemoveEmptySubnodes(); | 392 root->RemoveEmptySubnodes(); |
| 425 return true; | 393 return true; |
| 426 } | 394 } |
| 427 | 395 |
| 428 void QueryParser::ExtractQueryWords(const string16& text, | 396 void QueryParser::ExtractQueryWords(const string16& text, |
| 429 std::vector<QueryWord>* words) { | 397 std::vector<QueryWord>* words) { |
| 430 base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD); | 398 base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD); |
| 431 // TODO(evanm): support a locale here | 399 // TODO(evanm): support a locale here |
| 432 if (!iter.Init()) | 400 if (!iter.Init()) |
| 433 return; | 401 return; |
| 434 | 402 |
| 435 while (iter.Advance()) { | 403 while (iter.Advance()) { |
| 436 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It | 404 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It |
| 437 // is not necessarily a word, but could also be a sequence of punctuation | 405 // is not necessarily a word, but could also be a sequence of punctuation |
| 438 // or whitespace. | 406 // or whitespace. |
| 439 if (iter.IsWord()) { | 407 if (iter.IsWord()) { |
| 440 string16 word = iter.GetString(); | 408 string16 word = iter.GetString(); |
| 441 if (!word.empty()) { | 409 if (!word.empty()) { |
| 442 words->push_back(QueryWord()); | 410 words->push_back(QueryWord()); |
| 443 words->back().word = word; | 411 words->back().word = word; |
| 444 words->back().position = iter.prev(); | 412 words->back().position = iter.prev(); |
| 445 } | 413 } |
| 446 } | 414 } |
| 447 } | 415 } |
| 448 } | 416 } |
| OLD | NEW |