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

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: Change non-breaking characters to '.' and '-'. Created 8 years, 10 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
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 = i->second; 40 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);
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
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
134 return true; 144 return true;
135 } 145 }
136 } 146 }
137 return false; 147 return false;
138 } 148 }
139 149
140 void QueryNodeWord::AppendWords(std::vector<string16>* words) const { 150 void QueryNodeWord::AppendWords(std::vector<string16>* words) const {
141 words->push_back(word_); 151 words->push_back(word_);
142 } 152 }
143 153
154 void QueryNodeWord::Append(const string16& str) {
sky 2012/02/10 16:53:35 Definition should match declaration order (this sh
Patrick Dubroy 2012/02/20 11:00:36 Done.
155 word_ += str;
156 }
157
144 // A QueryNodeList has a collection of QueryNodes which are deleted in the end. 158 // A QueryNodeList has a collection of QueryNodes which are deleted in the end.
145 class QueryNodeList : public QueryNode { 159 class QueryNodeList : public QueryNode {
146 public: 160 public:
147 typedef std::vector<QueryNode*> QueryNodeVector; 161 typedef std::vector<QueryNode*> QueryNodeVector;
148 162
149 QueryNodeList(); 163 QueryNodeList();
150 virtual ~QueryNodeList(); 164 virtual ~QueryNodeList();
151 165
152 QueryNodeVector* children() { return &children_; } 166 QueryNodeVector* children() { return &children_; }
153 167
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after
340 if (!query_nodes[i]->HasMatchIn(query_words, &matches)) 354 if (!query_nodes[i]->HasMatchIn(query_words, &matches))
341 return false; 355 return false;
342 } 356 }
343 if (lower_text.length() != text.length()) { 357 if (lower_text.length() != text.length()) {
344 // The lower case string differs from the original string. The matches are 358 // The lower case string differs from the original string. The matches are
345 // meaningless. 359 // meaningless.
346 // TODO(sky): we need a better way to align the positions so that we don't 360 // TODO(sky): we need a better way to align the positions so that we don't
347 // completely punt here. 361 // completely punt here.
348 match_positions->clear(); 362 match_positions->clear();
349 } else { 363 } else {
350 CoalseAndSortMatchPositions(&matches); 364 CoalesceAndSortMatchPositions(&matches);
351 match_positions->swap(matches); 365 match_positions->swap(matches);
352 } 366 }
353 return true; 367 return true;
354 } 368 }
355 369
356 bool QueryParser::ParseQueryImpl(const string16& query, QueryNodeList* root) { 370 bool QueryParser::ParseQueryImpl(const string16& query, QueryNodeList* root) {
357 base::i18n::BreakIterator iter(query, base::i18n::BreakIterator::BREAK_WORD); 371 base::i18n::BreakIterator iter(query, base::i18n::BreakIterator::BREAK_WORD);
358 // TODO(evanm): support a locale here 372 // TODO(evanm): support a locale here
359 if (!iter.Init()) 373 if (!iter.Init())
360 return false; 374 return false;
361 375
362 // To handle nesting, we maintain a stack of QueryNodeLists. 376 // To handle nesting, we maintain a stack of QueryNodeLists.
363 // The last element (back) of the stack contains the current, deepest node. 377 // The last element (back) of the stack contains the current, deepest node.
364 std::vector<QueryNodeList*> query_stack; 378 std::vector<QueryNodeList*> query_stack;
365 query_stack.push_back(root); 379 query_stack.push_back(root);
Scott Hess - ex-Googler 2012/02/17 22:34:59 I don't understand why a stack is needed to parse
Patrick Dubroy 2012/02/20 11:00:36 I agree. I can fix this up in a follow-up CL if yo
366 380
367 bool in_quotes = false; // whether we're currently in a quoted phrase 381 bool in_quotes = false; // whether we're currently in a quoted phrase
382 QueryNodeWord* current_word = NULL;
368 while (iter.Advance()) { 383 while (iter.Advance()) {
369 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It 384 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It
370 // is not necessarily a word, but could also be a sequence of punctuation 385 // is not necessarily a word, it could also be a punctuation or whitespace
371 // or whitespace. 386 // character. Punctuation is preserved inside quotes, and otherwise removed
372 if (iter.IsWord()) { 387 // except if it is a non-breaking character in the middle of a word.
373 QueryNodeWord* word_node = new QueryNodeWord(iter.GetString()); 388
374 if (in_quotes) 389 wchar_t last_char = query[iter.prev()];
Scott Hess - ex-Googler 2012/02/17 22:34:59 I'm not sure "last_char" is the best description.
Patrick Dubroy 2012/02/20 11:00:36 You're right. Not sure why I named it like that. D
375 word_node->set_literal(true); 390 if (IsQueryQuote(last_char)) {
sky 2012/02/10 16:53:35 Should we require the trailing quote character to
mrossetti 2012/02/10 17:33:43 I almost commented on that as well but the existin
Patrick Dubroy 2012/02/20 11:00:36 I think we should. I can address this in a follow-
376 query_stack.back()->AddChild(word_node); 391 if (!in_quotes) {
377 } else { // Punctuation. 392 QueryNodeList* quotes_node = new QueryNodePhrase;
378 if (IsQueryQuote(query[iter.prev()])) { 393 query_stack.back()->AddChild(quotes_node);
379 if (!in_quotes) { 394 query_stack.push_back(quotes_node);
380 QueryNodeList* quotes_node = new QueryNodePhrase; 395 in_quotes = true;
381 query_stack.back()->AddChild(quotes_node); 396 } else {
382 query_stack.push_back(quotes_node); 397 query_stack.pop_back(); // Stop adding to the quoted phrase.
383 in_quotes = true; 398 in_quotes = false;
384 } else {
385 query_stack.pop_back(); // Stop adding to the quoted phrase.
386 in_quotes = false;
387 }
388 } 399 }
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.
Scott Hess - ex-Googler 2012/02/17 22:34:59 If it's not a word, is it always a single characte
Patrick Dubroy 2012/02/20 11:00:36 Done.
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());
Scott Hess - ex-Googler 2012/02/17 22:34:59 If I understand this right, I'm not sure I buy it.
Patrick Dubroy 2012/02/20 11:00:36 I agree. I don't think it's very common to include
419 else
420 current_word = NULL;
389 } 421 }
390 } 422 }
391 423
392 root->RemoveEmptySubnodes(); 424 root->RemoveEmptySubnodes();
393 return true; 425 return true;
394 } 426 }
395 427
396 void QueryParser::ExtractQueryWords(const string16& text, 428 void QueryParser::ExtractQueryWords(const string16& text,
397 std::vector<QueryWord>* words) { 429 std::vector<QueryWord>* words) {
398 base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD); 430 base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD);
399 // TODO(evanm): support a locale here 431 // TODO(evanm): support a locale here
400 if (!iter.Init()) 432 if (!iter.Init())
401 return; 433 return;
402 434
403 while (iter.Advance()) { 435 while (iter.Advance()) {
404 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It 436 // Just found a span between 'prev' (inclusive) and 'pos' (exclusive). It
405 // is not necessarily a word, but could also be a sequence of punctuation 437 // is not necessarily a word, but could also be a sequence of punctuation
406 // or whitespace. 438 // or whitespace.
407 if (iter.IsWord()) { 439 if (iter.IsWord()) {
408 string16 word = iter.GetString(); 440 string16 word = iter.GetString();
409 if (!word.empty()) { 441 if (!word.empty()) {
410 words->push_back(QueryWord()); 442 words->push_back(QueryWord());
411 words->back().word = word; 443 words->back().word = word;
412 words->back().position = iter.prev(); 444 words->back().position = iter.prev();
413 } 445 }
414 } 446 }
415 } 447 }
416 } 448 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698