Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(218)

Side by Side Diff: chrome/browser/history/query_parser.cc

Issue 9316129: Don't strip punctuation inside quotes in history search queries. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase. Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | chrome/browser/history/query_parser_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2011 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"
14 15
15 namespace { 16 namespace {
16 17
17 // Returns true if |mp1.first| is less than |mp2.first|. This is used to 18 // Returns true if |mp1.first| is less than |mp2.first|. This is used to
18 // sort match positions. 19 // sort match positions.
19 int CompareMatchPosition(const Snippet::MatchPosition& mp1, 20 int CompareMatchPosition(const Snippet::MatchPosition& mp1,
20 const Snippet::MatchPosition& mp2) { 21 const Snippet::MatchPosition& mp2) {
21 return mp1.first < mp2.first; 22 return mp1.first < mp2.first;
22 } 23 }
23 24
(...skipping 15 matching lines...) Expand all
39 mp.second = std::max(mp.second, i->second); 40 mp.second = std::max(mp.second, i->second);
40 i = matches->erase(i); 41 i = matches->erase(i);
41 } else { 42 } else {
42 return; 43 return;
43 } 44 }
44 } 45 }
45 } 46 }
46 47
47 // Sorts the match positions in |matches| by their first index, then coalesces 48 // Sorts the match positions in |matches| by their first index, then coalesces
48 // any match positions that intersect each other. 49 // any match positions that intersect each other.
49 void CoalseAndSortMatchPositions(Snippet::MatchPositions* matches) { 50 void CoalesceAndSortMatchPositions(Snippet::MatchPositions* matches) {
50 std::sort(matches->begin(), matches->end(), &CompareMatchPosition); 51 std::sort(matches->begin(), matches->end(), &CompareMatchPosition);
51 // WARNING: we don't use iterator here as CoalesceMatchesFrom may remove 52 // WARNING: we don't use iterator here as CoalesceMatchesFrom may remove
52 // from matches. 53 // from matches.
53 for (size_t i = 0; i < matches->size(); ++i) 54 for (size_t i = 0; i < matches->size(); ++i)
54 CoalesceMatchesFrom(i, matches); 55 CoalesceMatchesFrom(i, matches);
55 } 56 }
56 57
57 // Returns true if the character is considered a quote. 58 // Returns true if the character is considered a quote.
58 bool IsQueryQuote(wchar_t ch) { 59 bool IsQueryQuote(wchar_t ch) {
59 return ch == '"' || 60 return ch == '"' ||
60 ch == 0xab || // left pointing double angle bracket 61 ch == 0xab || // left pointing double angle bracket
61 ch == 0xbb || // right pointing double angle bracket 62 ch == 0xbb || // right pointing double angle bracket
62 ch == 0x201c || // left double quotation mark 63 ch == 0x201c || // left double quotation mark
63 ch == 0x201d || // right double quotation mark 64 ch == 0x201d || // right double quotation mark
64 ch == 0x201e; // double low-9 quotation mark 65 ch == 0x201e; // double low-9 quotation mark
65 } 66 }
66 67
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
67 } // namespace 75 } // namespace
68 76
69 // Inheritance structure: 77 // Inheritance structure:
70 // Queries are represented as trees of QueryNodes. 78 // Queries are represented as trees of QueryNodes.
71 // QueryNodes are either a collection of subnodes (a QueryNodeList) 79 // QueryNodes are either a collection of subnodes (a QueryNodeList)
72 // or a single word (a QueryNodeWord). 80 // or a single word (a QueryNodeWord).
73 81
74 // A QueryNodeWord is a single word in the query. 82 // A QueryNodeWord is a sequence of consecutive characters in a query.
83 // It can be an actual word or an URL-like sequence of characters.
75 class QueryNodeWord : public QueryNode { 84 class QueryNodeWord : public QueryNode {
76 public: 85 public:
77 explicit QueryNodeWord(const string16& word); 86 explicit QueryNodeWord(const string16& word);
78 virtual ~QueryNodeWord(); 87 virtual ~QueryNodeWord();
79 88
80 const string16& word() const { return word_; } 89 const string16& word() const { return word_; }
81 90
82 void set_literal(bool literal) { literal_ = literal; } 91 void set_literal(bool literal) { literal_ = literal; }
92 void Append(const string16& word);
Scott Hess - ex-Googler 2013/04/17 18:14:02 I think this sense of "Append" is enough different
83 93
84 // QueryNode: 94 // QueryNode:
85 virtual int AppendToSQLiteQuery(string16* query) const OVERRIDE; 95 virtual int AppendToSQLiteQuery(string16* query) const OVERRIDE;
86 virtual bool IsWord() const OVERRIDE; 96 virtual bool IsWord() const OVERRIDE;
87 virtual bool Matches(const string16& word, bool exact) const OVERRIDE; 97 virtual bool Matches(const string16& word, bool exact) const OVERRIDE;
88 virtual bool HasMatchIn( 98 virtual bool HasMatchIn(
89 const std::vector<QueryWord>& words, 99 const std::vector<QueryWord>& words,
90 Snippet::MatchPositions* match_positions) const OVERRIDE; 100 Snippet::MatchPositions* match_positions) const OVERRIDE;
91 virtual void AppendWords(std::vector<string16>* words) const OVERRIDE; 101 virtual void AppendWords(std::vector<string16>* words) const OVERRIDE;
92 102
93 private: 103 private:
94 string16 word_; 104 string16 word_;
95 bool literal_; 105 bool literal_;
96 106
97 DISALLOW_COPY_AND_ASSIGN(QueryNodeWord); 107 DISALLOW_COPY_AND_ASSIGN(QueryNodeWord);
98 }; 108 };
99 109
100 QueryNodeWord::QueryNodeWord(const string16& word) 110 QueryNodeWord::QueryNodeWord(const string16& word)
101 : word_(word), 111 : word_(word),
102 literal_(false) {} 112 literal_(false) {}
103 113
104 QueryNodeWord::~QueryNodeWord() {} 114 QueryNodeWord::~QueryNodeWord() {}
105 115
116 void QueryNodeWord::Append(const string16& str) {
117 word_ += str;
118 }
119
106 int QueryNodeWord::AppendToSQLiteQuery(string16* query) const { 120 int QueryNodeWord::AppendToSQLiteQuery(string16* query) const {
107 query->append(word_); 121 query->append(word_);
108 122
109 // Use prefix search if we're not literal and long enough. 123 // Use prefix search if we're not literal and long enough.
110 if (!literal_ && QueryParser::IsWordLongEnoughForPrefixSearch(word_)) 124 if (!literal_ && QueryParser::IsWordLongEnoughForPrefixSearch(word_))
111 *query += L'*'; 125 *query += L'*';
112 return 1; 126 return 1;
113 } 127 }
114 128
115 bool QueryNodeWord::IsWord() const { 129 bool QueryNodeWord::IsWord() const {
(...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after
341 if (!query_nodes[i]->HasMatchIn(query_words, &matches)) 355 if (!query_nodes[i]->HasMatchIn(query_words, &matches))
342 return false; 356 return false;
343 } 357 }
344 if (lower_text.length() != text.length()) { 358 if (lower_text.length() != text.length()) {
345 // The lower case string differs from the original string. The matches are 359 // The lower case string differs from the original string. The matches are
346 // meaningless. 360 // meaningless.
347 // TODO(sky): we need a better way to align the positions so that we don't 361 // TODO(sky): we need a better way to align the positions so that we don't
348 // completely punt here. 362 // completely punt here.
349 match_positions->clear(); 363 match_positions->clear();
350 } else { 364 } else {
351 CoalseAndSortMatchPositions(&matches); 365 CoalesceAndSortMatchPositions(&matches);
352 match_positions->swap(matches); 366 match_positions->swap(matches);
353 } 367 }
354 return true; 368 return true;
355 } 369 }
356 370
357 bool QueryParser::ParseQueryImpl(const string16& query, QueryNodeList* root) { 371 bool QueryParser::ParseQueryImpl(const string16& query, QueryNodeList* root) {
358 base::i18n::BreakIterator iter(query, base::i18n::BreakIterator::BREAK_WORD); 372 base::i18n::BreakIterator iter(query, base::i18n::BreakIterator::BREAK_WORD);
359 // TODO(evanm): support a locale here 373 // TODO(evanm): support a locale here
360 if (!iter.Init()) 374 if (!iter.Init())
361 return false; 375 return false;
362 376
363 // To handle nesting, we maintain a stack of QueryNodeLists. 377 // To handle nesting, we maintain a stack of QueryNodeLists.
364 // The last element (back) of the stack contains the current, deepest node. 378 // The last element (back) of the stack contains the current, deepest node.
365 std::vector<QueryNodeList*> query_stack; 379 std::vector<QueryNodeList*> query_stack;
366 query_stack.push_back(root); 380 query_stack.push_back(root);
367 381
368 bool in_quotes = false; // whether we're currently in a quoted phrase 382 bool in_quotes = false; // whether we're currently in a quoted phrase
383 QueryNodeWord* current_word = NULL;
369 while (iter.Advance()) { 384 while (iter.Advance()) {
370 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It 385 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It
371 // is not necessarily a word, but could also be a sequence of punctuation 386 // is not necessarily a word, it could also be a punctuation or whitespace
372 // or whitespace. 387 // character. Punctuation is preserved inside quotes, and otherwise removed
373 if (iter.IsWord()) { 388 // except if it is a non-breaking character in the middle of a word.
374 QueryNodeWord* word_node = new QueryNodeWord(iter.GetString()); 389
375 if (in_quotes) 390 wchar_t first_char = query[iter.prev()];
376 word_node->set_literal(true); 391 if (IsQueryQuote(first_char)) {
377 query_stack.back()->AddChild(word_node); 392 if (!in_quotes) {
378 } else { // Punctuation. 393 QueryNodeList* quotes_node = new QueryNodePhrase;
379 if (IsQueryQuote(query[iter.prev()])) { 394 query_stack.back()->AddChild(quotes_node);
380 if (!in_quotes) { 395 query_stack.push_back(quotes_node);
381 QueryNodeList* quotes_node = new QueryNodePhrase; 396 in_quotes = true;
382 query_stack.back()->AddChild(quotes_node); 397 } else {
383 query_stack.push_back(quotes_node); 398 query_stack.pop_back(); // Stop adding to the quoted phrase.
384 in_quotes = true; 399 in_quotes = false;
385 } else {
386 query_stack.pop_back(); // Stop adding to the quoted phrase.
387 in_quotes = false;
388 }
389 } 400 }
401 current_word = NULL;
402 } else if (iter.IsWord() || (in_quotes && !IsWhitespace(first_char))) {
Scott Hess - ex-Googler 2013/04/17 18:14:02 Previously, if there were a sequence of IsWord() t
403 // Append to the current word if the new token is a word or a non-
404 // whitespace token inside quotes.
405 if (current_word) {
406 current_word->Append(iter.GetString());
407 } else {
408 current_word = new QueryNodeWord(iter.GetString());
409 current_word->set_literal(in_quotes);
410 query_stack.back()->AddChild(current_word);
411 }
412 } else if (current_word != NULL) {
413 // Allow non-breaking symbols inside a word.
414 // Any other punctuation or whitespace character ends the current word.
415
416 // TODO(dubroy): Consider sharing code with the omnibox to allow a more
417 // accurate "best guess" of whether a sequence of characters is a URL.
418 if (IsNonBreakingSymbol(first_char))
419 current_word->Append(iter.GetString());
Scott Hess - ex-Googler 2013/04/17 18:14:02 I believe this won't back out the symbol(s) if the
420 else
421 current_word = NULL;
390 } 422 }
391 } 423 }
392 424
393 root->RemoveEmptySubnodes(); 425 root->RemoveEmptySubnodes();
394 return true; 426 return true;
395 } 427 }
396 428
397 void QueryParser::ExtractQueryWords(const string16& text, 429 void QueryParser::ExtractQueryWords(const string16& text,
398 std::vector<QueryWord>* words) { 430 std::vector<QueryWord>* words) {
399 base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD); 431 base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD);
400 // TODO(evanm): support a locale here 432 // TODO(evanm): support a locale here
401 if (!iter.Init()) 433 if (!iter.Init())
402 return; 434 return;
403 435
404 while (iter.Advance()) { 436 while (iter.Advance()) {
405 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It 437 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It
406 // is not necessarily a word, but could also be a sequence of punctuation 438 // is not necessarily a word, but could also be a sequence of punctuation
407 // or whitespace. 439 // or whitespace.
408 if (iter.IsWord()) { 440 if (iter.IsWord()) {
409 string16 word = iter.GetString(); 441 string16 word = iter.GetString();
410 if (!word.empty()) { 442 if (!word.empty()) {
411 words->push_back(QueryWord()); 443 words->push_back(QueryWord());
412 words->back().word = word; 444 words->back().word = word;
413 words->back().position = iter.prev(); 445 words->back().position = iter.prev();
414 } 446 }
415 } 447 }
416 } 448 }
417 } 449 }
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/history/query_parser_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698