Implement CreditCardField, a FormField that matches a credit card field in a form.

BUG=none
TEST=none
Review URL: http://codereview.chromium.org/502018

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34736 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/autofill/credit_card_field.cc b/chrome/browser/autofill/credit_card_field.cc
new file mode 100644
index 0000000..976ea07
--- /dev/null
+++ b/chrome/browser/autofill/credit_card_field.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/credit_card_field.h"
+
+#include "base/string16.h"
+#include "chrome/browser/autofill/autofill_field.h"
+
+bool CreditCardField::GetFieldInfo(FieldTypeMap* field_type_map) const {
+  return false;
+}
+
+// static
+CreditCardField* CreditCardField::Parse(
+    std::vector<AutoFillField*>::const_iterator* iter,
+    bool is_ecml) {
+  CreditCardField credit_card_field;
+  std::vector<AutoFillField*>::const_iterator q = *iter;
+  string16 pattern;
+
+  // Credit card fields can appear in many different orders.
+  for (int fields = 0; q != *iter; ++fields) {
+    // Sometimes the cardholder field is just labeled "name" (e.g. on test page
+    // Starbucks - Credit card.html).  Unfortunately this is a dangerously
+    // generic word to search for, since it will often match a name (not
+    // cardholder name) field before or after credit card fields.  So we search
+    // for "name" only when we've already parsed at least one other credit card
+    // field and haven't yet parsed the expiration date (which usually appears
+    // at the end).
+    if (credit_card_field.cardholder_ == NULL) {
+      string16 name_pattern;
+      if (is_ecml) {
+        name_pattern = GetEcmlPattern(kEcmlCardHolder);
+      } else {
+        if (fields == 0 || credit_card_field.expiration_month_) {
+          // at beginning or end
+          name_pattern = ASCIIToUTF16("card holder|name on card");
+        } else {
+          name_pattern = ASCIIToUTF16("name");
+        }
+      }
+
+      if (ParseText(&q, name_pattern, &credit_card_field.cardholder_)) {
+        continue;
+      }
+
+      // As a hard-coded hack for Expedia's billing pages (expedia_checkout.html
+      // and ExpediaBilling.html in our test suite), recognize separate fields
+      // for the cardholder's first and last name if they have the labels "cfnm"
+      // and "clnm".
+      std::vector<AutoFillField*>::const_iterator p = q;
+      AutoFillField* first;
+      if (!is_ecml && ParseText(&p, ASCIIToUTF16("^cfnm"), &first) &&
+          ParseText(&p, ASCIIToUTF16("^clnm"),
+                    &credit_card_field.cardholder_last_)) {
+        credit_card_field.cardholder_ = first;
+        q = p;
+        continue;
+      }
+    }
+
+    // TODO(jhawkins): Parse the type select control.
+
+    // We look for a card security code before we look for a credit card number
+    // and match the general term "number".  The security code has a plethora of
+    // names; we've seen "verification #", "verification number",
+    // "card identification number" and others listed in the ParseText() call
+    // below.
+    if (is_ecml) {
+      pattern = GetEcmlPattern(kEcmlCardVerification);
+    } else {
+      pattern = ASCIIToUTF16(
+          "verification|card identification|cvn|security code|cvv code|cvc");
+    }
+
+    if (credit_card_field.verification_ == NULL &&
+        ParseText(&q, pattern, &credit_card_field.verification_)) {
+      continue;
+    }
+
+    if (is_ecml)
+      pattern = GetEcmlPattern(kEcmlCardNumber);
+    else
+      pattern = ASCIIToUTF16("number|card #|card no.|card_number");
+
+    if (credit_card_field.number_ == NULL && ParseText(&q, pattern,
+        &credit_card_field.number_)) {
+      continue;
+    }
+
+    // "Expiration date" is the most common label here, but some pages have
+    // "Expires", "exp. date" or "exp. month" and "exp. year".  We also look for
+    // the field names ccmonth and ccyear, which appear on at least 4 of our
+    // test pages.
+    //
+    // -> On at least one page (The China Shop2.html) we find only the labels
+    // "month" and "year".  So for now we match these words directly; we'll
+    // see if this turns out to be too general.
+    //
+    // Toolbar Bug 51451: indeed, simply matching "month" is too general for
+    //   https://rps.fidelity.com/ftgw/rps/RtlCust/CreatePIN/Init.
+    // Instead, we match only words beginning with "month".
+    if (is_ecml)
+      pattern = GetEcmlPattern(kEcmlCardExpireMonth);
+    else
+      pattern = ASCIIToUTF16("expir|exp month|exp date|ccmonth|&month");
+
+    if (!credit_card_field.expiration_month_ ||
+        credit_card_field.expiration_month_->IsEmpty() &&
+        ParseText(&q, pattern, &credit_card_field.expiration_month_)) {
+      if (is_ecml)
+        pattern = GetEcmlPattern(kEcmlCardExpireYear);
+      else
+        pattern = ASCIIToUTF16("|exp|^/|ccyear|year");
+
+      if (!ParseText(&q, pattern, &credit_card_field.expiration_year_)) {
+        return NULL;
+      }
+
+      continue;
+    }
+
+    if (ParseText(&q, GetEcmlPattern(kEcmlCardExpireDay)))
+      continue;
+
+    // Some pages (e.g. ExpediaBilling.html) have a "card description"
+    // field; we parse this field but ignore it.
+    ParseText(&q, ASCIIToUTF16("card description"));
+  }
+
+  // On some pages, the user selects a card type using radio buttons
+  // (e.g. test page Apple Store Billing.html).  We can't handle that yet,
+  // so we treat the card type as optional for now.
+  if (credit_card_field.number_ != NULL &&
+      credit_card_field.expiration_month_ &&
+      !credit_card_field.expiration_month_->IsEmpty()) {
+      *iter = q;
+      return new CreditCardField(credit_card_field);
+  }
+
+  return NULL;
+}
+
+CreditCardField::CreditCardField()
+    : cardholder_(NULL),
+      cardholder_last_(NULL),
+      type_(NULL),
+      number_(NULL),
+      verification_(NULL),
+      expiration_month_(NULL),
+      expiration_year_(NULL) {
+}
+
+CreditCardField::CreditCardField(const CreditCardField& field)
+    : cardholder_(field.cardholder_),
+      cardholder_last_(field.cardholder_last_),
+      type_(field.type_),
+      number_(field.number_),
+      verification_(field.verification_),
+      expiration_month_(field.expiration_month_),
+      expiration_year_(field.expiration_year_) {
+}
diff --git a/chrome/browser/autofill/credit_card_field.h b/chrome/browser/autofill/credit_card_field.h
new file mode 100644
index 0000000..4fed417
--- /dev/null
+++ b/chrome/browser/autofill/credit_card_field.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_CREDIT_CARD_FIELD_H_
+#define CHROME_BROWSER_AUTOFILL_CREDIT_CARD_FIELD_H_
+
+#include <vector>
+
+#include "chrome/browser/autofill/form_field.h"
+
+class AutoFillField;
+
+class CreditCardField : public FormField {
+ public:
+  // FormField implementation:
+  virtual bool GetFieldInfo(FieldTypeMap* field_type_map) const;
+  virtual FormFieldType GetFormFieldType() { return kCreditCardType; }
+  virtual int priority() const { return 4; }
+
+  static CreditCardField* Parse(
+      std::vector<AutoFillField*>::const_iterator* iter,
+      bool is_ecml);
+
+ private:
+  CreditCardField();
+  explicit CreditCardField(const CreditCardField& field);
+  void operator=(const CreditCardField&);
+
+  AutoFillField* cardholder_;  // Optional.
+
+  // Occasionally pages have separate fields for the cardholder's first and
+  // last names; for such pages cardholder_ holds the first name field and
+  // we store the last name field here.
+  // (We could store an embedded NameField object here, but we don't do so
+  // because the text patterns for matching a cardholder name are different
+  // than for ordinary names, and because cardholder names never have titles,
+  // middle names or suffixes.)
+  AutoFillField* cardholder_last_;
+
+  AutoFillField* type_;  // Optional.  TODO(jhawkins): Parse the select control.
+  AutoFillField* number_;
+
+  // The 3-digit card verification number; we don't currently fill this.
+  AutoFillField* verification_;
+
+  // Both optional.  TODO(jhawkins): Parse the select control.
+  AutoFillField* expiration_month_;
+  AutoFillField* expiration_year_;
+};
+
+#endif  // CHROME_BROWSER_AUTOFILL_CREDIT_CARD_FIELD_H_
diff --git a/chrome/browser/autofill/form_field.cc b/chrome/browser/autofill/form_field.cc
index 1ab2c88..ec15120 100644
--- a/chrome/browser/autofill/form_field.cc
+++ b/chrome/browser/autofill/form_field.cc
@@ -6,6 +6,7 @@
 
 #include "chrome/browser/autofill/address_field.h"
 #include "chrome/browser/autofill/autofill_field.h"
+#include "chrome/browser/autofill/credit_card_field.h"
 #include "chrome/browser/autofill/phone_field.h"
 #include "third_party/WebKit/WebKit/chromium/public/WebRegularExpression.h"
 #include "third_party/WebKit/WebKit/chromium/public/WebString.h"
@@ -72,9 +73,11 @@
   field = AddressField::Parse(iter, is_ecml);
   if (field != NULL)
     return field;
+  field = CreditCardField::Parse(iter, is_ecml);
+  if (field != NULL)
+    return field;
 
   // TODO(jhawkins):
-  //  - CreditCardField
   //  - NameField
 
   return NULL;
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 70110e6..5004d95 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -90,6 +90,8 @@
         'browser/autofill/autofill_manager.h',
         'browser/autofill/autofill_type.cc',
         'browser/autofill/autofill_type.h',
+        'browser/autofill/credit_card_field.cc',
+        'browser/autofill/credit_card_field.h',
         'browser/autofill/field_types.h',
         'browser/autofill/form_field.cc',
         'browser/autofill/form_field.h',