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

Side by Side Diff: chrome/browser/autofill/address_field.cc

Issue 7891020: Make autofill regular expressions unicode again. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: remove extra gyp change Created 9 years, 3 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 | « build/escape_unicode.py ('k') | chrome/browser/autofill/autofill_regex_constants.h » ('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/autofill/address_field.h" 5 #include "chrome/browser/autofill/address_field.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 8
9 #include "base/logging.h" 9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h" 10 #include "base/memory/scoped_ptr.h"
11 #include "base/string16.h" 11 #include "base/string16.h"
12 #include "base/string_util.h" 12 #include "base/string_util.h"
13 #include "base/utf_string_conversions.h" 13 #include "base/utf_string_conversions.h"
14 #include "chrome/browser/autofill/autofill_field.h" 14 #include "chrome/browser/autofill/autofill_field.h"
15 #include "chrome/browser/autofill/autofill_regex_constants.h"
15 #include "chrome/browser/autofill/autofill_scanner.h" 16 #include "chrome/browser/autofill/autofill_scanner.h"
16 #include "chrome/browser/autofill/field_types.h" 17 #include "chrome/browser/autofill/field_types.h"
17 #include "ui/base/l10n/l10n_util.h" 18 #include "ui/base/l10n/l10n_util.h"
18 19
19 namespace {
20
21 // The UTF-8 version of these regular expressions are in
22 // regular_expressions.txt.
23 const char kAttentionIgnoredRe[] = "attention|attn";
24 const char kRegionIgnoredRe[] =
25 "province|region|other"
26 // es
27 "|provincia"
28 // pt-BR, pt-PT
29 "|bairro|suburb";
30 const char kCompanyRe[] =
31 "company|business|organization|organisation"
32 // de-DE
33 "|firma|firmenname"
34 // es
35 "|empresa"
36 // fr-FR
37 "|societe|soci\xc3\xa9t\xc3\xa9"
38 // it-IT
39 "|ragione.?sociale"
40 // ja-JP
41 "|\xe4\xbc\x9a\xe7\xa4\xbe"
42 // ru
43 "|\xd0\xbd\xd0\xb0\xd0\xb7\xd0\xb2\xd0\xb0\xd0\xbd\xd0\xb8\xd0\xb5.?\xd0"
44 "\xba\xd0\xbe\xd0\xbc\xd0\xbf\xd0\xb0\xd0\xbd\xd0\xb8\xd0\xb8"
45 // zh-CN
46 "|\xe5\x8d\x95\xe4\xbd\x8d|\xe5\x85\xac\xe5\x8f\xb8"
47 // ko-KR
48 "|\xed\x9a\x8c\xec\x82\xac|\xec\xa7\x81\xec\x9e\xa5";
49 const char kAddressLine1Re[] =
50 "address.*line|address1|addr1|street"
51 // de-DE
52 "|strasse|stra\xc3\x9f""e|hausnummer|housenumber"
53 // en-GB
54 "|house.?name"
55 // es
56 "|direccion|direcci\xc3\xb3n"
57 // fr-FR
58 "|adresse"
59 // it-IT
60 "|indirizzo"
61 // ja-JP
62 "|\xe4\xbd\x8f\xe6\x89\x80""1"
63 // pt-BR, pt-PT
64 "|morada|endere\xc3\xa7o"
65 // ru
66 "|\xd0\x90\xd0\xb4\xd1\x80\xd0\xb5\xd1\x81"
67 // zh-CN
68 "|\xe5\x9c\xb0\xe5\x9d\x80"
69 // ko-KR
70 "|\xec\xa3\xbc\xec\x86\x8c.?1";
71 const char kAddressLine1LabelRe[] =
72 "address"
73 // fr-FR
74 "|adresse"
75 // it-IT
76 "|indirizzo"
77 // ja-JP
78 "|\xe4\xbd\x8f\xe6\x89\x80"
79 // zh-CN
80 "|\xe5\x9c\xb0\xe5\x9d\x80"
81 // ko-KR
82 "|\xec\xa3\xbc\xec\x86\x8c";
83 const char kAddressLine2Re[] =
84 "address.*line2|address2|addr2|street|suite|unit"
85 // de-DE
86 "|adresszusatz|erg\xc3\xa4nzende.?angaben"
87 // es
88 "|direccion2|colonia|adicional"
89 // fr-FR
90 "|addresssuppl|complementnom|appartement"
91 // it-IT
92 "|indirizzo2"
93 // ja-JP
94 "|\xe4\xbd\x8f\xe6\x89\x80""2"
95 // pt-BR, pt-PT
96 "|complemento|addrcomplement"
97 // ru
98 "|\xd0\xa3\xd0\xbb\xd0\xb8\xd1\x86\xd0\xb0"
99 // zh-CN
100 "|\xe5\x9c\xb0\xe5\x9d\x80""2"
101 // ko-KR
102 "|\xec\xa3\xbc\xec\x86\x8c.?2";
103 const char kAddressLine2LabelRe[] =
104 "address"
105 // fr-FR
106 "|adresse"
107 // it-IT
108 "|indirizzo"
109 // zh-CN
110 "|\xe5\x9c\xb0\xe5\x9d\x80"
111 // ko-KR
112 "|\xec\xa3\xbc\xec\x86\x8c";
113 const char kAddressLine3Re[] =
114 "address.*line3|address3|addr3|street|line3"
115 // es
116 "|municipio"
117 // fr-FR
118 "|batiment|residence"
119 // it-IT
120 "|indirizzo3";
121 const char kCountryRe[] =
122 "country|countries|location"
123 // es
124 "|pa\xc3\xads|pais"
125 // ja-JP
126 "|\xe5\x9b\xbd"
127 // zh-CN
128 "|\xe5\x9b\xbd\xe5\xae\xb6"
129 // ko-KR
130 "|\xea\xb5\xad\xea\xb0\x80|\xeb\x82\x98\xeb\x9d\xbc";
131 const char kZipCodeRe[] =
132 "zip|postal|post.*code|pcode|^1z$"
133 // de-DE
134 "|postleitzahl"
135 // es
136 "|\\bcp\\b"
137 // fr-FR
138 "|\\bcdp\\b"
139 // it-IT
140 "|\\bcap\\b"
141 // ja-JP
142 "|\xe9\x83\xb5\xe4\xbe\xbf\xe7\x95\xaa\xe5\x8f\xb7"
143 // pt-BR, pt-PT
144 "|codigo|codpos|\\bcep\\b"
145 // ru
146 "|\xd0\x9f\xd0\xbe\xd1\x87\xd1\x82\xd0\xbe\xd0\xb2\xd1\x8b\xd0\xb9.?\xd0"
147 "\x98\xd0\xbd\xd0\xb4\xd0\xb5\xd0\xba\xd1\x81"
148 // zh-CN
149 "|\xe9\x82\xae\xe6\x94\xbf\xe7\xbc\x96\xe7\xa0\x81|\xe9\x82\xae\xe7\xbc"
150 "\x96"
151 // zh-TW
152 "|\xe9\x83\xb5\xe9\x81\x9e\xe5\x8d\x80\xe8\x99\x9f"
153 // ko-KR
154 "|\xec\x9a\xb0\xed\x8e\xb8.?\xeb\xb2\x88\xed\x98\xb8";
155 const char kZip4Re[] =
156 "zip|^-$|post2"
157 // pt-BR, pt-PT
158 "|codpos2";
159 const char kCityRe[] =
160 "city|town"
161 // de-DE
162 "|\\bort\\b|stadt"
163 // en-AU
164 "|suburb"
165 // es
166 "|ciudad|provincia|localidad|poblacion"
167 // fr-FR
168 "|ville|commune"
169 // it-IT
170 "|localita"
171 // ja-JP
172 "|\xe5\xb8\x82\xe5\x8c\xba\xe7\x94\xba\xe6\x9d\x91"
173 // pt-BR, pt-PT
174 "|cidade"
175 // ru
176 "|\xd0\x93\xd0\xbe\xd1\x80\xd0\xbe\xd0\xb4"
177 // zh-CN
178 "|\xe5\xb8\x82"
179 // zh-TW
180 "|\xe5\x88\x86\xe5\x8d\x80"
181 // ko-KR
182 "|^\xec\x8b\x9c[^\xeb\x8f\x84\xc2\xb7\xe3\x83\xbb]|\xec\x8b\x9c[\xc2\xb7"
183 "\xe3\x83\xbb]?\xea\xb5\xb0[\xc2\xb7\xe3\x83\xbb]?\xea\xb5\xac";
184 const char kStateRe[] =
185 "(?<!united )state|county|region|province"
186 // de-DE
187 "|land"
188 // en-UK
189 "|county|principality"
190 // ja-JP
191 "|\xe9\x83\xbd\xe9\x81\x93\xe5\xba\x9c\xe7\x9c\x8c"
192 // pt-BR, pt-PT
193 "|estado|provincia"
194 // ru
195 "|\xd0\xbe\xd0\xb1\xd0\xbb\xd0\xb0\xd1\x81\xd1\x82\xd1\x8c"
196 // zh-CN
197 "|\xe7\x9c\x81"
198 // zh-TW
199 "|\xe5\x9c\xb0\xe5\x8d\x80"
200 // ko-KR
201 "|^\xec\x8b\x9c[\xc2\xb7\xe3\x83\xbb]?\xeb\x8f\x84";
202 const char kAddressTypeSameAsRe[] = "same as";
203 const char kAddressTypeUseMyRe[] = "use my";
204 const char kBillingDesignatorRe[] = "bill";
205 const char kShippingDesignatorRe[] = "ship";
206
207 } // namespace
208
209 FormField* AddressField::Parse(AutofillScanner* scanner) { 20 FormField* AddressField::Parse(AutofillScanner* scanner) {
210 if (scanner->IsEnd()) 21 if (scanner->IsEnd())
211 return NULL; 22 return NULL;
212 23
213 scoped_ptr<AddressField> address_field(new AddressField); 24 scoped_ptr<AddressField> address_field(new AddressField);
214 const AutofillField* const initial_field = scanner->Cursor(); 25 const AutofillField* const initial_field = scanner->Cursor();
215 size_t saved_cursor = scanner->SaveCursor(); 26 size_t saved_cursor = scanner->SaveCursor();
216 27
217 string16 attention_ignored = UTF8ToUTF16(kAttentionIgnoredRe); 28 string16 attention_ignored = UTF8ToUTF16(autofill::kAttentionIgnoredRe);
218 string16 region_ignored = UTF8ToUTF16(kRegionIgnoredRe); 29 string16 region_ignored = UTF8ToUTF16(autofill::kRegionIgnoredRe);
219 30
220 // Allow address fields to appear in any order. 31 // Allow address fields to appear in any order.
221 size_t begin_trailing_non_labeled_fields = 0; 32 size_t begin_trailing_non_labeled_fields = 0;
222 bool has_trailing_non_labeled_fields = false; 33 bool has_trailing_non_labeled_fields = false;
223 while (!scanner->IsEnd()) { 34 while (!scanner->IsEnd()) {
224 const size_t cursor = scanner->SaveCursor(); 35 const size_t cursor = scanner->SaveCursor();
225 if (ParseAddressLines(scanner, address_field.get()) || 36 if (ParseAddressLines(scanner, address_field.get()) ||
226 ParseCity(scanner, address_field.get()) || 37 ParseCity(scanner, address_field.get()) ||
227 ParseState(scanner, address_field.get()) || 38 ParseState(scanner, address_field.get()) ||
228 ParseZipCode(scanner, address_field.get()) || 39 ParseZipCode(scanner, address_field.get()) ||
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after
369 ok = ok && AddClassification(country_, address_country, map); 180 ok = ok && AddClassification(country_, address_country, map);
370 return ok; 181 return ok;
371 } 182 }
372 183
373 // static 184 // static
374 bool AddressField::ParseCompany(AutofillScanner* scanner, 185 bool AddressField::ParseCompany(AutofillScanner* scanner,
375 AddressField* address_field) { 186 AddressField* address_field) {
376 if (address_field->company_ && !address_field->company_->IsEmpty()) 187 if (address_field->company_ && !address_field->company_->IsEmpty())
377 return false; 188 return false;
378 189
379 return ParseField(scanner, UTF8ToUTF16(kCompanyRe), &address_field->company_); 190 return ParseField(scanner, UTF8ToUTF16(autofill::kCompanyRe),
191 &address_field->company_);
380 } 192 }
381 193
382 // static 194 // static
383 bool AddressField::ParseAddressLines(AutofillScanner* scanner, 195 bool AddressField::ParseAddressLines(AutofillScanner* scanner,
384 AddressField* address_field) { 196 AddressField* address_field) {
385 // We only match the string "address" in page text, not in element names, 197 // We only match the string "address" in page text, not in element names,
386 // because sometimes every element in a group of address fields will have 198 // because sometimes every element in a group of address fields will have
387 // a name containing the string "address"; for example, on the page 199 // a name containing the string "address"; for example, on the page
388 // Kohl's - Register Billing Address.html the text element labeled "city" 200 // Kohl's - Register Billing Address.html the text element labeled "city"
389 // has the name "BILL_TO_ADDRESS<>city". We do match address labels 201 // has the name "BILL_TO_ADDRESS<>city". We do match address labels
390 // such as "address1", which appear as element names on various pages (eg 202 // such as "address1", which appear as element names on various pages (eg
391 // AmericanGirl-Registration.html, BloomingdalesBilling.html, 203 // AmericanGirl-Registration.html, BloomingdalesBilling.html,
392 // EBay Registration Enter Information.html). 204 // EBay Registration Enter Information.html).
393 if (address_field->address1_) 205 if (address_field->address1_)
394 return false; 206 return false;
395 207
396 string16 pattern = UTF8ToUTF16(kAddressLine1Re); 208 string16 pattern = UTF8ToUTF16(autofill::kAddressLine1Re);
397 string16 label_pattern = UTF8ToUTF16(kAddressLine1LabelRe); 209 string16 label_pattern = UTF8ToUTF16(autofill::kAddressLine1LabelRe);
398 210
399 if (!ParseField(scanner, pattern, &address_field->address1_) && 211 if (!ParseField(scanner, pattern, &address_field->address1_) &&
400 !ParseFieldSpecifics(scanner, label_pattern, MATCH_LABEL | MATCH_TEXT, 212 !ParseFieldSpecifics(scanner, label_pattern, MATCH_LABEL | MATCH_TEXT,
401 &address_field->address1_)) { 213 &address_field->address1_)) {
402 return false; 214 return false;
403 } 215 }
404 216
405 // Optionally parse more address lines, which may have empty labels. 217 // Optionally parse more address lines, which may have empty labels.
406 // Some pages have 3 address lines (eg SharperImageModifyAccount.html) 218 // Some pages have 3 address lines (eg SharperImageModifyAccount.html)
407 // Some pages even have 4 address lines (e.g. uk/ShoesDirect2.html)! 219 // Some pages even have 4 address lines (e.g. uk/ShoesDirect2.html)!
408 pattern = UTF8ToUTF16(kAddressLine2Re); 220 pattern = UTF8ToUTF16(autofill::kAddressLine2Re);
409 label_pattern = UTF8ToUTF16(kAddressLine2LabelRe); 221 label_pattern = UTF8ToUTF16(autofill::kAddressLine2LabelRe);
410 if (!ParseEmptyLabel(scanner, &address_field->address2_) && 222 if (!ParseEmptyLabel(scanner, &address_field->address2_) &&
411 !ParseField(scanner, pattern, &address_field->address2_)) { 223 !ParseField(scanner, pattern, &address_field->address2_)) {
412 ParseFieldSpecifics(scanner, label_pattern, MATCH_LABEL | MATCH_TEXT, 224 ParseFieldSpecifics(scanner, label_pattern, MATCH_LABEL | MATCH_TEXT,
413 &address_field->address2_); 225 &address_field->address2_);
414 } 226 }
415 227
416 // Try for a third line, which we will promptly discard. 228 // Try for a third line, which we will promptly discard.
417 if (address_field->address2_ != NULL) { 229 if (address_field->address2_ != NULL) {
418 pattern = UTF8ToUTF16(kAddressLine3Re); 230 pattern = UTF8ToUTF16(autofill::kAddressLine3Re);
419 ParseField(scanner, pattern, NULL); 231 ParseField(scanner, pattern, NULL);
420 } 232 }
421 233
422 return true; 234 return true;
423 } 235 }
424 236
425 // static 237 // static
426 bool AddressField::ParseCountry(AutofillScanner* scanner, 238 bool AddressField::ParseCountry(AutofillScanner* scanner,
427 AddressField* address_field) { 239 AddressField* address_field) {
428 // Parse a country. The occasional page (e.g. 240 // Parse a country. The occasional page (e.g.
429 // Travelocity_New Member Information1.html) calls this a "location". 241 // Travelocity_New Member Information1.html) calls this a "location".
430 if (address_field->country_ && !address_field->country_->IsEmpty()) 242 if (address_field->country_ && !address_field->country_->IsEmpty())
431 return false; 243 return false;
432 244
433 return ParseFieldSpecifics(scanner, 245 return ParseFieldSpecifics(scanner,
434 UTF8ToUTF16(kCountryRe), 246 UTF8ToUTF16(autofill::kCountryRe),
435 MATCH_DEFAULT | MATCH_SELECT, 247 MATCH_DEFAULT | MATCH_SELECT,
436 &address_field->country_); 248 &address_field->country_);
437 } 249 }
438 250
439 // static 251 // static
440 bool AddressField::ParseZipCode(AutofillScanner* scanner, 252 bool AddressField::ParseZipCode(AutofillScanner* scanner,
441 AddressField* address_field) { 253 AddressField* address_field) {
442 // Parse a zip code. On some UK pages (e.g. The China Shop2.html) this 254 // Parse a zip code. On some UK pages (e.g. The China Shop2.html) this
443 // is called a "post code". 255 // is called a "post code".
444 // 256 //
445 // HACK: Just for the MapQuest driving directions page we match the 257 // HACK: Just for the MapQuest driving directions page we match the
446 // exact name "1z", which MapQuest uses to label its zip code field. 258 // exact name "1z", which MapQuest uses to label its zip code field.
447 // Hopefully before long we'll be smart enough to find the zip code 259 // Hopefully before long we'll be smart enough to find the zip code
448 // on that page automatically. 260 // on that page automatically.
449 if (address_field->zip_) 261 if (address_field->zip_)
450 return false; 262 return false;
451 263
452 string16 pattern = UTF8ToUTF16(kZipCodeRe); 264 string16 pattern = UTF8ToUTF16(autofill::kZipCodeRe);
453 if (!ParseField(scanner, pattern, &address_field->zip_)) 265 if (!ParseField(scanner, pattern, &address_field->zip_))
454 return false; 266 return false;
455 267
456 address_field->type_ = kGenericAddress; 268 address_field->type_ = kGenericAddress;
457 // Look for a zip+4, whose field name will also often contain 269 // Look for a zip+4, whose field name will also often contain
458 // the substring "zip". 270 // the substring "zip".
459 ParseField(scanner, 271 ParseField(scanner,
460 UTF8ToUTF16(kZip4Re), 272 UTF8ToUTF16(autofill::kZip4Re),
461 &address_field->zip4_); 273 &address_field->zip4_);
462 274
463 return true; 275 return true;
464 } 276 }
465 277
466 // static 278 // static
467 bool AddressField::ParseCity(AutofillScanner* scanner, 279 bool AddressField::ParseCity(AutofillScanner* scanner,
468 AddressField* address_field) { 280 AddressField* address_field) {
469 // Parse a city name. Some UK pages (e.g. The China Shop2.html) use 281 // Parse a city name. Some UK pages (e.g. The China Shop2.html) use
470 // the term "town". 282 // the term "town".
471 if (address_field->city_) 283 if (address_field->city_)
472 return false; 284 return false;
473 285
474 // Select fields are allowed here. This occurs on top-100 site rediff.com. 286 // Select fields are allowed here. This occurs on top-100 site rediff.com.
475 return ParseFieldSpecifics(scanner, 287 return ParseFieldSpecifics(scanner,
476 UTF8ToUTF16(kCityRe), 288 UTF8ToUTF16(autofill::kCityRe),
477 MATCH_DEFAULT | MATCH_SELECT, 289 MATCH_DEFAULT | MATCH_SELECT,
478 &address_field->city_); 290 &address_field->city_);
479 } 291 }
480 292
481 // static 293 // static
482 bool AddressField::ParseState(AutofillScanner* scanner, 294 bool AddressField::ParseState(AutofillScanner* scanner,
483 AddressField* address_field) { 295 AddressField* address_field) {
484 if (address_field->state_) 296 if (address_field->state_)
485 return false; 297 return false;
486 298
487 return ParseFieldSpecifics(scanner, 299 return ParseFieldSpecifics(scanner,
488 UTF8ToUTF16(kStateRe), 300 UTF8ToUTF16(autofill::kStateRe),
489 MATCH_DEFAULT | MATCH_SELECT, 301 MATCH_DEFAULT | MATCH_SELECT,
490 &address_field->state_); 302 &address_field->state_);
491 } 303 }
492 304
493 AddressField::AddressType AddressField::AddressTypeFromText( 305 AddressField::AddressType AddressField::AddressTypeFromText(
494 const string16 &text) { 306 const string16 &text) {
495 if (text.find(UTF8ToUTF16(kAddressTypeSameAsRe)) != string16::npos || 307 size_t same_as = text.find(UTF8ToUTF16(autofill::kAddressTypeSameAsRe));
496 text.find(UTF8ToUTF16(kAddressTypeUseMyRe)) != string16::npos) 308 size_t use_shipping = text.find(UTF8ToUTF16(autofill::kAddressTypeUseMyRe));
309 if (same_as != string16::npos || use_shipping != string16::npos)
497 // This text could be a checkbox label such as "same as my billing 310 // This text could be a checkbox label such as "same as my billing
498 // address" or "use my shipping address". 311 // address" or "use my shipping address".
499 // ++ It would help if we generally skipped all text that appears 312 // ++ It would help if we generally skipped all text that appears
500 // after a check box. 313 // after a check box.
501 return kGenericAddress; 314 return kGenericAddress;
502 315
503 // Not all pages say "billing address" and "shipping address" explicitly; 316 // Not all pages say "billing address" and "shipping address" explicitly;
504 // for example, Craft Catalog1.html has "Bill-to Address" and 317 // for example, Craft Catalog1.html has "Bill-to Address" and
505 // "Ship-to Address". 318 // "Ship-to Address".
506 size_t bill = text.rfind(UTF8ToUTF16(kBillingDesignatorRe)); 319 size_t bill = text.rfind(UTF8ToUTF16(autofill::kBillingDesignatorRe));
507 size_t ship = text.rfind(UTF8ToUTF16(kShippingDesignatorRe)); 320 size_t ship = text.rfind(UTF8ToUTF16(autofill::kShippingDesignatorRe));
508 321
509 if (bill == string16::npos && ship == string16::npos) 322 if (bill == string16::npos && ship == string16::npos)
510 return kGenericAddress; 323 return kGenericAddress;
511 324
512 if (bill != string16::npos && ship == string16::npos) 325 if (bill != string16::npos && ship == string16::npos)
513 return kBillingAddress; 326 return kBillingAddress;
514 327
515 if (bill == string16::npos && ship != string16::npos) 328 if (bill == string16::npos && ship != string16::npos)
516 return kShippingAddress; 329 return kShippingAddress;
517 330
518 if (bill > ship) 331 if (bill > ship)
519 return kBillingAddress; 332 return kBillingAddress;
520 333
521 return kShippingAddress; 334 return kShippingAddress;
522 } 335 }
OLDNEW
« no previous file with comments | « build/escape_unicode.py ('k') | chrome/browser/autofill/autofill_regex_constants.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698