From 9836c295763a661274dd56207fe0c4888f989515 Mon Sep 17 00:00:00 2001
From: Hamza Hejja <49674728+hamzahejja@users.noreply.github.com>
Date: Mon, 17 Feb 2020 21:05:27 +0200
Subject: [PATCH] feat(isPassportNumber): add isPassportNumber validator
(#1250)
* feat: implement isPassportNumber validator
* normalize str by cleaning spaces and converting to UPPERCASE
* style: function docs, helper comments
* remove unrelated changes
closes #1218
---
README.md | 1 +
index.js | 6 +
lib/isPassportNumber.js | 112 ++++++++
src/index.js | 2 +
src/lib/isPassportNumber.js | 64 +++++
test/validators.js | 516 ++++++++++++++++++++++++++++++++++++
validator.js | 105 ++++++++
validator.min.js | 2 +-
8 files changed, 807 insertions(+), 1 deletion(-)
create mode 100644 lib/isPassportNumber.js
create mode 100644 src/lib/isPassportNumber.js
diff --git a/README.md b/README.md
index 671d4862c..e3533e1ae 100644
--- a/README.md
+++ b/README.md
@@ -140,6 +140,7 @@ Validator | Description
**isMultibyte(str)** | check if the string contains one or more multibyte chars.
**isNumeric(str [, options])** | check if the string contains only numbers.
`options` is an object which defaults to `{no_symbols: false}`. If `no_symbols` is true, the validator will reject numeric strings that feature a symbol (e.g. `+`, `-`, or `.`).
**isOctal(str)** | check if the string is a valid octal number.
+**isPassportNumber(str, countryCode) | check if the string is a valid passport number relative to a specific country code.
**isPort(str)** | check if the string is a valid port number.
**isPostalCode(str, locale)** | check if the string is a postal code,
(locale is one of `[ 'AD', 'AT', 'AU', 'BE', 'BG', 'BR', 'CA', 'CH', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'ID', 'IE' 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'KE', 'LI', 'LT', 'LU', 'LV', 'MT', 'MX', 'NL', 'NO', 'NZ', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SI', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM' ]` OR 'any'. If 'any' is used, function will check if any of the locals match. Locale list is `validator.isPostalCodeLocales`.).
**isSemVer(str)** | check if the string is a Semantic Versioning Specification (SemVer).
diff --git a/index.js b/index.js
index 75851f449..b02bf86ae 100644
--- a/index.js
+++ b/index.js
@@ -35,12 +35,16 @@ var _isFQDN = _interopRequireDefault(require("./lib/isFQDN"));
var _isBoolean = _interopRequireDefault(require("./lib/isBoolean"));
+var _isLocale = _interopRequireDefault(require("./lib/isLocale"));
+
var _isAlpha = _interopRequireWildcard(require("./lib/isAlpha"));
var _isAlphanumeric = _interopRequireWildcard(require("./lib/isAlphanumeric"));
var _isNumeric = _interopRequireDefault(require("./lib/isNumeric"));
+var _isPassportNumber = _interopRequireDefault(require("./lib/isPassportNumber"));
+
var _isPort = _interopRequireDefault(require("./lib/isPort"));
var _isLowercase = _interopRequireDefault(require("./lib/isLowercase"));
@@ -201,6 +205,7 @@ var validator = {
isAlphanumeric: _isAlphanumeric.default,
isAlphanumericLocales: _isAlphanumeric.locales,
isNumeric: _isNumeric.default,
+ isPassportNumber: _isPassportNumber.default,
isPort: _isPort.default,
isLowercase: _isLowercase.default,
isUppercase: _isUppercase.default,
@@ -227,6 +232,7 @@ var validator = {
isJSON: _isJSON.default,
isEmpty: _isEmpty.default,
isLength: _isLength.default,
+ isLocale: _isLocale.default,
isByteLength: _isByteLength.default,
isUUID: _isUUID.default,
isMongoId: _isMongoId.default,
diff --git a/lib/isPassportNumber.js b/lib/isPassportNumber.js
new file mode 100644
index 000000000..6b101bc60
--- /dev/null
+++ b/lib/isPassportNumber.js
@@ -0,0 +1,112 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = isPassportNumber;
+
+/**
+ * Reference:
+ * https://en.wikipedia.org/ -- Wikipedia
+ * https://docs.microsoft.com/en-us/microsoft-365/compliance/eu-passport-number -- EU Passport Number
+ * https://countrycode.org/ -- Country Codes
+ */
+var passportRegexByCountryCode = {
+ AM: /^[A-Z]{2}\d{7}$/,
+ // ARMENIA
+ AR: /^[A-Z]{3}\d{6}$/,
+ // ARGENTINA
+ AT: /^[A-Z]\d{7}$/,
+ // AUSTRIA
+ AU: /^[A-Z]\d{7}$/,
+ // AUSTRALIA
+ BE: /^[A-Z]{2}\d{6}$/,
+ // BELGIUM
+ BG: /^\d{9}$/,
+ // BULGARIA
+ CA: /^[A-Z]{2}\d{6}$/,
+ // CANADA
+ CH: /^[A-Z]\d{7}$/,
+ // SWITZERLAND
+ CN: /^[GE]\d{8}$/,
+ // CHINA [G=Ordinary, E=Electronic] followed by 8-digits
+ CY: /^[A-Z](\d{6}|\d{8})$/,
+ // CYPRUS
+ CZ: /^\d{8}$/,
+ // CZECH REPUBLIC
+ DE: /^[CFGHJKLMNPRTVWXYZ0-9]{9}$/,
+ // GERMANY
+ DK: /^\d{9}$/,
+ // DENMARK
+ EE: /^([A-Z]\d{7}|[A-Z]{2}\d{7})$/,
+ // ESTONIA (K followed by 7-digits), e-passports have 2 UPPERCASE followed by 7 digits
+ ES: /^[A-Z0-9]{2}([A-Z0-9]?)\d{6}$/,
+ // SPAIN
+ FI: /^[A-Z]{2}\d{7}$/,
+ // FINLAND
+ FR: /^\d{2}[A-Z]{2}\d{5}$/,
+ // FRANCE
+ GB: /^\d{9}$/,
+ // UNITED KINGDOM
+ GR: /^[A-Z]{2}\d{7}$/,
+ // GREECE
+ HR: /^\d{9}$/,
+ // CROATIA
+ HU: /^[A-Z]{2}(\d{6}|\d{7})$/,
+ // HUNGARY
+ IE: /^[A-Z0-9]{2}\d{7}$/,
+ // IRELAND
+ IS: /^(A)\d{7}$/,
+ // ICELAND
+ IT: /^[A-Z0-9]{2}\d{7}$/,
+ // ITALY
+ JP: /^[A-Z]{2}\d{7}$/,
+ // JAPAN
+ KR: /^[MS]\d{8}$/,
+ // SOUTH KOREA, REPUBLIC OF KOREA, [S=PS Passports, M=PM Passports]
+ LT: /^[A-Z0-9]{8}$/,
+ // LITHUANIA
+ LU: /^[A-Z0-9]{8}$/,
+ // LUXEMBURG
+ LV: /^[A-Z0-9]{2}\d{7}$/,
+ // LATVIA
+ MT: /^\d{7}$/,
+ // MALTA
+ NL: /^[A-Z]{2}[A-Z0-9]{6}\d$/,
+ // NETHERLANDS
+ PO: /^[A-Z]{2}\d{7}$/,
+ // POLAND
+ PT: /^[A-Z]\d{6}$/,
+ // PORTUGAL
+ RO: /^\d{8,9}$/,
+ // ROMANIA
+ SE: /^\d{8}$/,
+ // SWEDEN
+ SL: /^(P)[A-Z]\d{7}$/,
+ // SLOVANIA
+ SK: /^[0-9A-Z]\d{7}$/,
+ // SLOVAKIA
+ TR: /^[A-Z]\d{8}$/,
+ // TURKEY
+ UA: /^[A-Z]{2}\d{6}$/,
+ // UKRAINE
+ US: /^\d{9}$/ // UNITED STATES
+
+};
+/**
+ * Check if str is a valid passport number
+ * relative to provided ISO Country Code.
+ *
+ * @param {string} str
+ * @param {string} countryCode
+ * @return {boolean}
+ */
+
+function isPassportNumber(str, countryCode) {
+ /** Remove All Whitespaces, Convert to UPPERCASE */
+ var normalizedStr = str.replace(/\s/g, '').toUpperCase();
+ return countryCode.toUpperCase() in passportRegexByCountryCode && passportRegexByCountryCode[countryCode].test(normalizedStr);
+}
+
+module.exports = exports.default;
+module.exports.default = exports.default;
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 4bf522d34..68645f78e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -19,6 +19,7 @@ import isLocale from './lib/isLocale';
import isAlpha, { locales as isAlphaLocales } from './lib/isAlpha';
import isAlphanumeric, { locales as isAlphanumericLocales } from './lib/isAlphanumeric';
import isNumeric from './lib/isNumeric';
+import isPassportNumber from './lib/isPassportNumber';
import isPort from './lib/isPort';
import isLowercase from './lib/isLowercase';
import isUppercase from './lib/isUppercase';
@@ -134,6 +135,7 @@ const validator = {
isAlphanumeric,
isAlphanumericLocales,
isNumeric,
+ isPassportNumber,
isPort,
isLowercase,
isUppercase,
diff --git a/src/lib/isPassportNumber.js b/src/lib/isPassportNumber.js
new file mode 100644
index 000000000..304f72fae
--- /dev/null
+++ b/src/lib/isPassportNumber.js
@@ -0,0 +1,64 @@
+/**
+ * Reference:
+ * https://en.wikipedia.org/ -- Wikipedia
+ * https://docs.microsoft.com/en-us/microsoft-365/compliance/eu-passport-number -- EU Passport Number
+ * https://countrycode.org/ -- Country Codes
+ */
+const passportRegexByCountryCode = {
+ AM: /^[A-Z]{2}\d{7}$/, // ARMENIA
+ AR: /^[A-Z]{3}\d{6}$/, // ARGENTINA
+ AT: /^[A-Z]\d{7}$/, // AUSTRIA
+ AU: /^[A-Z]\d{7}$/, // AUSTRALIA
+ BE: /^[A-Z]{2}\d{6}$/, // BELGIUM
+ BG: /^\d{9}$/, // BULGARIA
+ CA: /^[A-Z]{2}\d{6}$/, // CANADA
+ CH: /^[A-Z]\d{7}$/, // SWITZERLAND
+ CN: /^[GE]\d{8}$/, // CHINA [G=Ordinary, E=Electronic] followed by 8-digits
+ CY: /^[A-Z](\d{6}|\d{8})$/, // CYPRUS
+ CZ: /^\d{8}$/, // CZECH REPUBLIC
+ DE: /^[CFGHJKLMNPRTVWXYZ0-9]{9}$/, // GERMANY
+ DK: /^\d{9}$/, // DENMARK
+ EE: /^([A-Z]\d{7}|[A-Z]{2}\d{7})$/, // ESTONIA (K followed by 7-digits), e-passports have 2 UPPERCASE followed by 7 digits
+ ES: /^[A-Z0-9]{2}([A-Z0-9]?)\d{6}$/, // SPAIN
+ FI: /^[A-Z]{2}\d{7}$/, // FINLAND
+ FR: /^\d{2}[A-Z]{2}\d{5}$/, // FRANCE
+ GB: /^\d{9}$/, // UNITED KINGDOM
+ GR: /^[A-Z]{2}\d{7}$/, // GREECE
+ HR: /^\d{9}$/, // CROATIA
+ HU: /^[A-Z]{2}(\d{6}|\d{7})$/, // HUNGARY
+ IE: /^[A-Z0-9]{2}\d{7}$/, // IRELAND
+ IS: /^(A)\d{7}$/, // ICELAND
+ IT: /^[A-Z0-9]{2}\d{7}$/, // ITALY
+ JP: /^[A-Z]{2}\d{7}$/, // JAPAN
+ KR: /^[MS]\d{8}$/, // SOUTH KOREA, REPUBLIC OF KOREA, [S=PS Passports, M=PM Passports]
+ LT: /^[A-Z0-9]{8}$/, // LITHUANIA
+ LU: /^[A-Z0-9]{8}$/, // LUXEMBURG
+ LV: /^[A-Z0-9]{2}\d{7}$/, // LATVIA
+ MT: /^\d{7}$/, // MALTA
+ NL: /^[A-Z]{2}[A-Z0-9]{6}\d$/, // NETHERLANDS
+ PO: /^[A-Z]{2}\d{7}$/, // POLAND
+ PT: /^[A-Z]\d{6}$/, // PORTUGAL
+ RO: /^\d{8,9}$/, // ROMANIA
+ SE: /^\d{8}$/, // SWEDEN
+ SL: /^(P)[A-Z]\d{7}$/, // SLOVANIA
+ SK: /^[0-9A-Z]\d{7}$/, // SLOVAKIA
+ TR: /^[A-Z]\d{8}$/, // TURKEY
+ UA: /^[A-Z]{2}\d{6}$/, // UKRAINE
+ US: /^\d{9}$/, // UNITED STATES
+};
+
+/**
+ * Check if str is a valid passport number
+ * relative to provided ISO Country Code.
+ *
+ * @param {string} str
+ * @param {string} countryCode
+ * @return {boolean}
+ */
+export default function isPassportNumber(str, countryCode) {
+ /** Remove All Whitespaces, Convert to UPPERCASE */
+ const normalizedStr = str.replace(/\s/g, '').toUpperCase();
+
+ return (countryCode.toUpperCase() in passportRegexByCountryCode) &&
+ passportRegexByCountryCode[countryCode].test(normalizedStr);
+}
diff --git a/test/validators.js b/test/validators.js
index 404c1d928..b5e413403 100644
--- a/test/validators.js
+++ b/test/validators.js
@@ -1853,6 +1853,522 @@ describe('Validators', () => {
});
});
+ it('should validate passport number', () => {
+ test({
+ validator: 'isPassportNumber',
+ args: ['AM'],
+ valid: [
+ 'AF0549358',
+ ],
+ invalid: [
+ 'A1054935',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['AR'],
+ valid: [
+ 'AAC811035',
+ ],
+ invalid: [
+ 'A11811035',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['AT'],
+ valid: [
+ 'P 1630837',
+ 'P 4366918',
+ ],
+ invalid: [
+ '0 1630837',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['AU'],
+ valid: [
+ 'N0995852',
+ 'L4819236',
+ ],
+ invalid: [
+ '1A012345',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['BE'],
+ valid: [
+ 'EM000000',
+ 'LA080402',
+ ],
+ invalid: [
+ '00123456',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['BG'],
+ valid: [
+ '346395366',
+ '039903356',
+ ],
+ invalid: [
+ 'ABC123456',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['CA'],
+ valid: [
+ 'GA302922',
+ 'ZE000509',
+ ],
+ invalid: [
+ 'AB0123456',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['CH'],
+ valid: [
+ 'S1100409',
+ 'S5200073',
+ 'X4028791',
+ ],
+ invalid: [
+ 'AB123456',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['CN'],
+ valid: [
+ 'G25352389',
+ 'E00160027',
+ ],
+ invalid: [
+ 'K0123456',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['CY'],
+ valid: [
+ 'K00000413',
+ ],
+ invalid: [
+ 'K10100',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['CZ'],
+ valid: [
+ '99003853',
+ '42747260',
+ ],
+ invalid: [
+ '012345678',
+ 'AB123456',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['DE'],
+ valid: [
+ 'C01X00T47',
+ 'C26VMVVC3',
+ ],
+ invalid: [
+ 'AS0123456',
+ 'A012345678',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['DK'],
+ valid: [
+ '900010172',
+ ],
+ invalid: [
+ '01234567',
+ 'K01234567',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['EE'],
+ valid: [
+ 'K4218285',
+ 'K3295867',
+ 'KB0167630',
+ 'VD0023777',
+ ],
+ invalid: [
+ 'K01234567',
+ 'KB00112233',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['ES'],
+ valid: [
+ 'AF238143',
+ 'ZAB000254',
+ ],
+ invalid: [
+ 'AF01234567',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['FI'],
+ valid: [
+ 'XP8271602',
+ 'XD8500003',
+ ],
+ invalid: [
+ 'A01234567',
+ 'ABC012345',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['FR'],
+ valid: [
+ '10CV28144',
+ '60RF19342',
+ '05RP34083',
+ ],
+ invalid: [
+ '012345678',
+ 'AB0123456',
+ '01C234567',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['GB'],
+ valid: [
+ '925076473',
+ '107182890',
+ '104121156',
+ ],
+ invalid: [
+ 'A012345678',
+ 'K000000000',
+ '0123456789',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['GR'],
+ valid: [
+ 'AE0000005',
+ 'AK0219304',
+ ],
+ invalid: [
+ 'A01234567',
+ '012345678',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['HR'],
+ valid: [
+ '007007007',
+ '138463188',
+ ],
+ invalid: [
+ 'A01234567',
+ '00112233',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['HU'],
+ valid: [
+ 'ZA084505',
+ 'BA0006902',
+ ],
+ invalid: [
+ 'A01234567',
+ '012345678',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['IE'],
+ valid: [
+ 'D23145890',
+ 'X65097105',
+ 'XN0019390',
+ ],
+ invalid: [
+ 'XND012345',
+ '0123456789',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['IS'],
+ valid: [
+ 'A2040611',
+ 'A1197783',
+ ],
+ invalid: [
+ 'K0000000',
+ '01234567',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['IT'],
+ valid: [
+ 'YA8335453',
+ 'KK0000000',
+ ],
+ invalid: [
+ '01234567',
+ 'KAK001122',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['JP'],
+ valid: [
+ 'NH1106002',
+ 'TE3180251',
+ 'XS1234567',
+ ],
+ invalid: [
+ 'X12345678',
+ '012345678',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['KR'],
+ valid: [
+ 'M35772699',
+ 'M70689098',
+ ],
+ invalid: [
+ 'X12345678',
+ '012345678',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['LT'],
+ valid: [
+ '20200997',
+ 'LB311756',
+ ],
+ invalid: [
+ 'LB01234567',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['LU'],
+ valid: [
+ 'JCU9J4T2',
+ 'JC4E7L2H',
+ ],
+ invalid: [
+ 'JCU9J4T',
+ 'JC4E7L2H0',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['LV'],
+ valid: [
+ 'LV9000339',
+ 'LV4017173',
+ ],
+ invalid: [
+ 'LV01234567',
+ '4017173LV',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['MT'],
+ valid: [
+ '1026564',
+ ],
+ invalid: [
+ '01234567',
+ 'MT01234',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['NL'],
+ valid: [
+ 'XTR110131',
+ 'XR1001R58',
+ ],
+ invalid: [
+ 'XTR11013R',
+ 'XR1001R58A',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['PO'],
+ valid: [
+ 'ZS 0000177',
+ 'AN 3000011',
+ ],
+ invalid: [
+ 'A1 0000177',
+ '012345678',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['PT'],
+ valid: [
+ 'I700044',
+ 'K453286',
+ ],
+ invalid: [
+ '0700044',
+ 'K4532861',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['RO'],
+ valid: [
+ '05485968',
+ '040005646',
+ ],
+ invalid: [
+ 'R05485968',
+ '0511060461',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['SE'],
+ valid: [
+ '59000001',
+ '56702928',
+ ],
+ invalid: [
+ 'SE012345',
+ '012345678',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['SL'],
+ valid: [
+ 'PB0036440',
+ 'PB1390281',
+ ],
+ invalid: [
+ 'SL0123456',
+ 'P01234567',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['SK'],
+ valid: [
+ 'P0000000',
+ ],
+ invalid: [
+ 'SK012345',
+ '012345678',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['TR'],
+ valid: [
+ 'U 06764100',
+ 'U 01048537',
+ ],
+ invalid: [
+ '06764100U',
+ '010485371',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['UA'],
+ valid: [
+ 'EH345655',
+ 'EK000001',
+ 'AP841503',
+ ],
+ invalid: [
+ '01234567',
+ '012345EH',
+ 'A012345P',
+ ],
+ });
+
+ test({
+ validator: 'isPassportNumber',
+ args: ['US'],
+ valid: [
+ '790369937',
+ '340007237',
+ ],
+ invalid: [
+ 'US0123456',
+ '0123456US',
+ '7903699371',
+ ],
+ });
+ });
+
it('should validate decimal numbers', () => {
test({
validator: 'isDecimal',
diff --git a/validator.js b/validator.js
index 96e0e000b..645c79876 100644
--- a/validator.js
+++ b/validator.js
@@ -884,6 +884,109 @@ function isNumeric(str, options) {
return numeric.test(str);
}
+/**
+ * Reference:
+ * https://en.wikipedia.org/ -- Wikipedia
+ * https://docs.microsoft.com/en-us/microsoft-365/compliance/eu-passport-number -- EU Passport Number
+ * https://countrycode.org/ -- Country Codes
+ */
+var passportRegexByCountryCode = {
+ AM: /^[A-Z]{2}\d{7}$/,
+ // ARMENIA
+ AR: /^[A-Z]{3}\d{6}$/,
+ // ARGENTINA
+ AT: /^[A-Z]\d{7}$/,
+ // AUSTRIA
+ AU: /^[A-Z]\d{7}$/,
+ // AUSTRALIA
+ BE: /^[A-Z]{2}\d{6}$/,
+ // BELGIUM
+ BG: /^\d{9}$/,
+ // BULGARIA
+ CA: /^[A-Z]{2}\d{6}$/,
+ // CANADA
+ CH: /^[A-Z]\d{7}$/,
+ // SWITZERLAND
+ CN: /^[GE]\d{8}$/,
+ // CHINA [G=Ordinary, E=Electronic] followed by 8-digits
+ CY: /^[A-Z](\d{6}|\d{8})$/,
+ // CYPRUS
+ CZ: /^\d{8}$/,
+ // CZECH REPUBLIC
+ DE: /^[CFGHJKLMNPRTVWXYZ0-9]{9}$/,
+ // GERMANY
+ DK: /^\d{9}$/,
+ // DENMARK
+ EE: /^([A-Z]\d{7}|[A-Z]{2}\d{7})$/,
+ // ESTONIA (K followed by 7-digits), e-passports have 2 UPPERCASE followed by 7 digits
+ ES: /^[A-Z0-9]{2}([A-Z0-9]?)\d{6}$/,
+ // SPAIN
+ FI: /^[A-Z]{2}\d{7}$/,
+ // FINLAND
+ FR: /^\d{2}[A-Z]{2}\d{5}$/,
+ // FRANCE
+ GB: /^\d{9}$/,
+ // UNITED KINGDOM
+ GR: /^[A-Z]{2}\d{7}$/,
+ // GREECE
+ HR: /^\d{9}$/,
+ // CROATIA
+ HU: /^[A-Z]{2}(\d{6}|\d{7})$/,
+ // HUNGARY
+ IE: /^[A-Z0-9]{2}\d{7}$/,
+ // IRELAND
+ IS: /^(A)\d{7}$/,
+ // ICELAND
+ IT: /^[A-Z0-9]{2}\d{7}$/,
+ // ITALY
+ JP: /^[A-Z]{2}\d{7}$/,
+ // JAPAN
+ KR: /^[MS]\d{8}$/,
+ // SOUTH KOREA, REPUBLIC OF KOREA, [S=PS Passports, M=PM Passports]
+ LT: /^[A-Z0-9]{8}$/,
+ // LITHUANIA
+ LU: /^[A-Z0-9]{8}$/,
+ // LUXEMBURG
+ LV: /^[A-Z0-9]{2}\d{7}$/,
+ // LATVIA
+ MT: /^\d{7}$/,
+ // MALTA
+ NL: /^[A-Z]{2}[A-Z0-9]{6}\d$/,
+ // NETHERLANDS
+ PO: /^[A-Z]{2}\d{7}$/,
+ // POLAND
+ PT: /^[A-Z]\d{6}$/,
+ // PORTUGAL
+ RO: /^\d{8,9}$/,
+ // ROMANIA
+ SE: /^\d{8}$/,
+ // SWEDEN
+ SL: /^(P)[A-Z]\d{7}$/,
+ // SLOVANIA
+ SK: /^[0-9A-Z]\d{7}$/,
+ // SLOVAKIA
+ TR: /^[A-Z]\d{8}$/,
+ // TURKEY
+ UA: /^[A-Z]{2}\d{6}$/,
+ // UKRAINE
+ US: /^\d{9}$/ // UNITED STATES
+
+};
+/**
+ * Check if str is a valid passport number
+ * relative to provided ISO Country Code.
+ *
+ * @param {string} str
+ * @param {string} countryCode
+ * @return {boolean}
+ */
+
+function isPassportNumber(str, countryCode) {
+ /** Remove All Whitespaces, Convert to UPPERCASE */
+ var normalizedStr = str.replace(/\s/g, '').toUpperCase();
+ return countryCode.toUpperCase() in passportRegexByCountryCode && passportRegexByCountryCode[countryCode].test(normalizedStr);
+}
+
var _int = /^(?:[-+]?(?:0|[1-9][0-9]*))$/;
var intLeadingZeroes = /^[-+]?[0-9]+$/;
function isInt(str, options) {
@@ -2394,6 +2497,7 @@ var validator = {
isAlphanumeric: isAlphanumeric,
isAlphanumericLocales: locales$2,
isNumeric: isNumeric,
+ isPassportNumber: isPassportNumber,
isPort: isPort,
isLowercase: isLowercase,
isUppercase: isUppercase,
@@ -2420,6 +2524,7 @@ var validator = {
isJSON: isJSON,
isEmpty: isEmpty,
isLength: isLength,
+ isLocale: isLocale,
isByteLength: isByteLength,
isUUID: isUUID,
isMongoId: isMongoId,
diff --git a/validator.min.js b/validator.min.js
index a2f8abf4d..4ead8d57b 100644
--- a/validator.min.js
+++ b/validator.min.js
@@ -20,4 +20,4 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.validator=e()}(this,function(){"use strict";function a(t){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function g(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){if(!(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t)))return;var r=[],n=!0,o=!1,i=void 0;try{for(var a,s=t[Symbol.iterator]();!(n=(a=s.next()).done)&&(r.push(a.value),!e||r.length!==e);n=!0);}catch(t){o=!0,i=t}finally{try{n||null==s.return||s.return()}finally{if(o)throw i}}return r}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function h(t){var e;if(!("string"==typeof t||t instanceof String))throw e=null===t?"null":"object"===(e=a(t))&&t.constructor&&t.constructor.hasOwnProperty("name")?t.constructor.name:"a ".concat(e),new TypeError("Expected string but received ".concat(e,"."))}function o(t){return h(t),t=Date.parse(t),isNaN(t)?null:new Date(t)}for(var t,r={"en-US":/^[A-Z]+$/i,"bg-BG":/^[А-Я]+$/i,"cs-CZ":/^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i,"da-DK":/^[A-ZÆØÅ]+$/i,"de-DE":/^[A-ZÄÖÜß]+$/i,"el-GR":/^[Α-ώ]+$/i,"es-ES":/^[A-ZÁÉÍÑÓÚÜ]+$/i,"fr-FR":/^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i,"it-IT":/^[A-ZÀÉÈÌÎÓÒÙ]+$/i,"nb-NO":/^[A-ZÆØÅ]+$/i,"nl-NL":/^[A-ZÁÉËÏÓÖÜÚ]+$/i,"nn-NO":/^[A-ZÆØÅ]+$/i,"hu-HU":/^[A-ZÁÉÍÓÖŐÚÜŰ]+$/i,"pl-PL":/^[A-ZĄĆĘŚŁŃÓŻŹ]+$/i,"pt-PT":/^[A-ZÃÁÀÂÄÇÉÊËÍÏÕÓÔÖÚÜ]+$/i,"ru-RU":/^[А-ЯЁ]+$/i,"sl-SI":/^[A-ZČĆĐŠŽ]+$/i,"sk-SK":/^[A-ZÁČĎÉÍŇÓŠŤÚÝŽĹŔĽÄÔ]+$/i,"sr-RS@latin":/^[A-ZČĆŽŠĐ]+$/i,"sr-RS":/^[А-ЯЂЈЉЊЋЏ]+$/i,"sv-SE":/^[A-ZÅÄÖ]+$/i,"tr-TR":/^[A-ZÇĞİıÖŞÜ]+$/i,"uk-UA":/^[А-ЩЬЮЯЄIЇҐі]+$/i,"ku-IQ":/^[ئابپتجچحخدرڕزژسشعغفڤقکگلڵمنوۆھەیێيطؤثآإأكضصةظذ]+$/i,ar:/^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/,he:/^[א-ת]+$/,"fa-IR":/^['آابپتثجچهخدذرزژسشصضطظعغفقکگلمنوهی']+$/i},n={"en-US":/^[0-9A-Z]+$/i,"bg-BG":/^[0-9А-Я]+$/i,"cs-CZ":/^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i,"da-DK":/^[0-9A-ZÆØÅ]+$/i,"de-DE":/^[0-9A-ZÄÖÜß]+$/i,"el-GR":/^[0-9Α-ω]+$/i,"es-ES":/^[0-9A-ZÁÉÍÑÓÚÜ]+$/i,"fr-FR":/^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i,"it-IT":/^[0-9A-ZÀÉÈÌÎÓÒÙ]+$/i,"hu-HU":/^[0-9A-ZÁÉÍÓÖŐÚÜŰ]+$/i,"nb-NO":/^[0-9A-ZÆØÅ]+$/i,"nl-NL":/^[0-9A-ZÁÉËÏÓÖÜÚ]+$/i,"nn-NO":/^[0-9A-ZÆØÅ]+$/i,"pl-PL":/^[0-9A-ZĄĆĘŚŁŃÓŻŹ]+$/i,"pt-PT":/^[0-9A-ZÃÁÀÂÄÇÉÊËÍÏÕÓÔÖÚÜ]+$/i,"ru-RU":/^[0-9А-ЯЁ]+$/i,"sl-SI":/^[0-9A-ZČĆĐŠŽ]+$/i,"sk-SK":/^[0-9A-ZÁČĎÉÍŇÓŠŤÚÝŽĹŔĽÄÔ]+$/i,"sr-RS@latin":/^[0-9A-ZČĆŽŠĐ]+$/i,"sr-RS":/^[0-9А-ЯЂЈЉЊЋЏ]+$/i,"sv-SE":/^[0-9A-ZÅÄÖ]+$/i,"tr-TR":/^[0-9A-ZÇĞİıÖŞÜ]+$/i,"uk-UA":/^[0-9А-ЩЬЮЯЄIЇҐі]+$/i,"ku-IQ":/^[٠١٢٣٤٥٦٧٨٩0-9ئابپتجچحخدرڕزژسشعغفڤقکگلڵمنوۆھەیێيطؤثآإأكضصةظذ]+$/i,ar:/^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/,he:/^[0-9א-ת]+$/,"fa-IR":/^['0-9آابپتثجچهخدذرزژسشصضطظعغفقکگلمنوهی۱۲۳۴۵۶۷۸۹۰']+$/i},i={"en-US":".",ar:"٫"},e=["AU","GB","HK","IN","NZ","ZA","ZM"],s=0;s=e.min)&&(!e.hasOwnProperty("max")||n<=e.max)&&(!e.hasOwnProperty("lt")||ne.gt)}r["pt-BR"]=r["pt-PT"],n["pt-BR"]=n["pt-PT"],i["pt-BR"]=i["pt-PT"],r["pl-Pl"]=r["pl-PL"],n["pl-Pl"]=n["pl-PL"],i["pl-Pl"]=i["pl-PL"];var m=Object.keys(i);function v(t){return p(t)?parseFloat(t):NaN}function _(t){return"object"===a(t)&&null!==t?t="function"==typeof t.toString?t.toString():"[object Object]":(null==t||isNaN(t)&&!t.length)&&(t=""),String(t)}function Z(t,e){var r=0a)return!1;if("::"===t)return!0;"::"===t.substr(0,2)?(n.shift(),n.shift(),o=!0):"::"===t.substr(t.length-2)&&(n.pop(),n.pop(),o=!0);for(var s=0;s$/i,N=/^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i,T=/^[a-z\d]+$/,x=/^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i,B=/^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i,w=/^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*$/i;var G={protocols:["http","https","ftp"],require_tld:!0,require_protocol:!1,require_host:!0,require_valid_protocol:!0,allow_underscores:!1,allow_trailing_dot:!1,allow_protocol_relative_urls:!1},b=/^\[([^\]]+)\](?::([0-9]+))?$/;function O(t,e){for(var r=0;r=e.min,o=!e.hasOwnProperty("max")||t<=e.max,i=!e.hasOwnProperty("lt")||te.gt;return r.test(t)&&n&&o&&i&&a}var Q=/^[\x00-\x7F]+$/;var X=/[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/;var q=/[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/;var tt=/[^\x00-\x7F]/;var et=function(t,e){var r=1]/.test(r)){if(!e)return;if(!(r.split('"').length===r.split('\\"').length))return}return 1}}(n))return!1}else if(e.require_display_name)return!1}if(!e.ignore_max_length&&254]/.test(t))return!1;if(0===t.indexOf("mailto:"))return!1;var r,n,o,i,a,s,l,u;if(e=Z(e,G),1<(l=(t=(l=(t=(l=t.split("#")).shift()).split("?")).shift()).split("://")).length){if(r=l.shift().toLowerCase(),e.require_valid_protocol&&-1===e.protocols.indexOf(r))return!1}else{if(e.require_protocol)return!1;if("//"===t.substr(0,2)){if(!e.allow_protocol_relative_urls)return!1;l[0]=t.substr(2)}}if(""===(t=l.join("://")))return!1;if(""===(t=(l=t.split("/")).shift())&&!e.require_host)return!0;if(1<(l=t.split("@")).length){if(e.disallow_auth)return!1;if(0<=(n=l.shift()).indexOf(":")&&2/g,">").replace(/\//g,"/").replace(/\\/g,"\").replace(/`/g,"`")},unescape:function(t){return h(t),t.replace(/&/g,"&").replace(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(///g,"/").replace(/\/g,"\\").replace(/`/g,"`")},stripLow:function(t,e){return h(t),de(t,e?"\\x00-\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F":"\\x00-\\x1F\\x7F")},whitelist:function(t,e){return h(t),t.replace(new RegExp("[^".concat(e,"]+"),"g"),"")},blacklist:de,isWhitelisted:function(t,e){h(t);for(var r=t.length-1;0<=r;r--)if(-1===e.indexOf(t[r]))return!1;return!0},normalizeEmail:function(t,e){e=Z(e,fe);var r=t.split("@"),n=r.pop(),o=[r.join("@"),n];if(o[1]=o[1].toLowerCase(),"gmail.com"===o[1]||"googlemail.com"===o[1]){if(e.gmail_remove_subaddress&&(o[0]=o[0].split("+")[0]),e.gmail_remove_dots&&(o[0]=o[0].replace(/\.+/g,he)),!o[0].length)return!1;(e.all_lowercase||e.gmail_lowercase)&&(o[0]=o[0].toLowerCase()),o[1]=e.gmail_convert_googlemaildotcom?"gmail.com":o[1]}else if(0<=Ae.indexOf(o[1])){if(e.icloud_remove_subaddress&&(o[0]=o[0].split("+")[0]),!o[0].length)return!1;(e.all_lowercase||e.icloud_lowercase)&&(o[0]=o[0].toLowerCase())}else if(0<=$e.indexOf(o[1])){if(e.outlookdotcom_remove_subaddress&&(o[0]=o[0].split("+")[0]),!o[0].length)return!1;(e.all_lowercase||e.outlookdotcom_lowercase)&&(o[0]=o[0].toLowerCase())}else if(0<=pe.indexOf(o[1])){if(e.yahoo_remove_subaddress){var i=o[0].split("-");o[0]=1=e.min)&&(!e.hasOwnProperty("max")||n<=e.max)&&(!e.hasOwnProperty("lt")||ne.gt)}r["pt-BR"]=r["pt-PT"],n["pt-BR"]=n["pt-PT"],i["pt-BR"]=i["pt-PT"],r["pl-Pl"]=r["pl-PL"],n["pl-Pl"]=n["pl-PL"],i["pl-Pl"]=i["pl-PL"];var m=Object.keys(i);function v(t){return p(t)?parseFloat(t):NaN}function Z(t){return"object"===a(t)&&null!==t?t="function"==typeof t.toString?t.toString():"[object Object]":(null==t||isNaN(t)&&!t.length)&&(t=""),String(t)}function S(t,e){var r=0a)return!1;if("::"===t)return!0;"::"===t.substr(0,2)?(n.shift(),n.shift(),o=!0):"::"===t.substr(t.length-2)&&(n.pop(),n.pop(),o=!0);for(var s=0;s$/i,N=/^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i,T=/^[a-z\d]+$/,B=/^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i,x=/^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i,w=/^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*$/i;var G={protocols:["http","https","ftp"],require_tld:!0,require_protocol:!1,require_host:!0,require_valid_protocol:!0,allow_underscores:!1,allow_trailing_dot:!1,allow_protocol_relative_urls:!1},U=/^\[([^\]]+)\](?::([0-9]+))?$/;function b(t,e){for(var r=0;r=e.min,o=!e.hasOwnProperty("max")||t<=e.max,i=!e.hasOwnProperty("lt")||te.gt;return r.test(t)&&n&&o&&i&&a}var X=/^[\x00-\x7F]+$/;var q=/[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/;var tt=/[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/;var et=/[^\x00-\x7F]/;var rt=function(t,e){var r=1]/.test(r)){if(!e)return;if(!(r.split('"').length===r.split('\\"').length))return}return 1}}(n))return!1}else if(e.require_display_name)return!1}if(!e.ignore_max_length&&254]/.test(t))return!1;if(0===t.indexOf("mailto:"))return!1;var r,n,o,i,a,s,l,u;if(e=S(e,G),1<(l=(t=(l=(t=(l=t.split("#")).shift()).split("?")).shift()).split("://")).length){if(r=l.shift().toLowerCase(),e.require_valid_protocol&&-1===e.protocols.indexOf(r))return!1}else{if(e.require_protocol)return!1;if("//"===t.substr(0,2)){if(!e.allow_protocol_relative_urls)return!1;l[0]=t.substr(2)}}if(""===(t=l.join("://")))return!1;if(""===(t=(l=t.split("/")).shift())&&!e.require_host)return!0;if(1<(l=t.split("@")).length){if(e.disallow_auth)return!1;if(0<=(n=l.shift()).indexOf(":")&&2/g,">").replace(/\//g,"/").replace(/\\/g,"\").replace(/`/g,"`")},unescape:function(t){return h(t),t.replace(/&/g,"&").replace(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(///g,"/").replace(/\/g,"\\").replace(/`/g,"`")},stripLow:function(t,e){return h(t),fe(t,e?"\\x00-\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F":"\\x00-\\x1F\\x7F")},whitelist:function(t,e){return h(t),t.replace(new RegExp("[^".concat(e,"]+"),"g"),"")},blacklist:fe,isWhitelisted:function(t,e){h(t);for(var r=t.length-1;0<=r;r--)if(-1===e.indexOf(t[r]))return!1;return!0},normalizeEmail:function(t,e){e=S(e,Ae);var r=t.split("@"),n=r.pop(),o=[r.join("@"),n];if(o[1]=o[1].toLowerCase(),"gmail.com"===o[1]||"googlemail.com"===o[1]){if(e.gmail_remove_subaddress&&(o[0]=o[0].split("+")[0]),e.gmail_remove_dots&&(o[0]=o[0].replace(/\.+/g,me)),!o[0].length)return!1;(e.all_lowercase||e.gmail_lowercase)&&(o[0]=o[0].toLowerCase()),o[1]=e.gmail_convert_googlemaildotcom?"gmail.com":o[1]}else if(0<=$e.indexOf(o[1])){if(e.icloud_remove_subaddress&&(o[0]=o[0].split("+")[0]),!o[0].length)return!1;(e.all_lowercase||e.icloud_lowercase)&&(o[0]=o[0].toLowerCase())}else if(0<=pe.indexOf(o[1])){if(e.outlookdotcom_remove_subaddress&&(o[0]=o[0].split("+")[0]),!o[0].length)return!1;(e.all_lowercase||e.outlookdotcom_lowercase)&&(o[0]=o[0].toLowerCase())}else if(0<=ge.indexOf(o[1])){if(e.yahoo_remove_subaddress){var i=o[0].split("-");o[0]=1