Skip to content

Commit

Permalink
feat(isEAN): implement isEAN validator (#1244)
Browse files Browse the repository at this point in the history
* feat(isEAN): implement isEAN validator

* add testcase to cover 0 as EAN check digit

* modify EAN regex to avoid lookbehind assertions
  • Loading branch information
hamzahejja authored Feb 5, 2020
1 parent 46585ef commit fc253c4
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Validator | Description
**isIP(str [, version])** | check if the string is an IP (version 4 or 6).
**isIPRange(str)** | check if the string is an IP Range(version 4 only).
**isISBN(str [, version])** | check if the string is an ISBN (version 10 or 13).
**isEAN(str)** | check if the string is an EAN (European Article Number).
**isISIN(str)** | check if the string is an [ISIN][ISIN] (stock/security identifier).
**isISO31661Alpha2(str)** | check if the string is a valid [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) officially assigned country code.
**isISO31661Alpha3(str)** | check if the string is a valid [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) officially assigned country code.
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ var _isCreditCard = _interopRequireDefault(require("./lib/isCreditCard"));

var _isIdentityCard = _interopRequireDefault(require("./lib/isIdentityCard"));

var _isEAN = _interopRequireDefault(require("./lib/isEAN"));

var _isISIN = _interopRequireDefault(require("./lib/isISIN"));

var _isISBN = _interopRequireDefault(require("./lib/isISBN"));
Expand Down Expand Up @@ -225,6 +227,7 @@ var validator = {
isIn: _isIn.default,
isCreditCard: _isCreditCard.default,
isIdentityCard: _isIdentityCard.default,
isEAN: _isEAN.default,
isISIN: _isISIN.default,
isISBN: _isISBN.default,
isISSN: _isISSN.default,
Expand Down
80 changes: 80 additions & 0 deletions lib/isEAN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = isEAN;

var _assertString = _interopRequireDefault(require("./util/assertString"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
* The most commonly used EAN standard is
* the thirteen-digit EAN-13, while the
* less commonly used 8-digit EAN-8 barcode was
* introduced for use on small packages.
* EAN consists of:
* GS1 prefix, manufacturer code, product code and check digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number
*/

/**
* Define EAN Lenghts; 8 for EAN-8; 13 for EAN-13
* and Regular Expression for valid EANs (EAN-8, EAN-13),
* with exact numberic matching of 8 or 13 digits [0-9]
*/
var LENGTH_EAN_8 = 8;
var validEanRegex = /^(\d{8}|\d{13})$/;
/**
* Get position weight given:
* EAN length and digit index/position
*
* @param {number} length
* @param {number} index
* @return {number}
*/

function getPositionWeightThroughLengthAndIndex(length, index) {
if (length === LENGTH_EAN_8) {
return index % 2 === 0 ? 3 : 1;
}

return index % 2 === 0 ? 1 : 3;
}
/**
* Calculate EAN Check Digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number#Calculation_of_checksum_digit
*
* @param {string} ean
* @return {number}
*/


function calculateCheckDigit(ean) {
var checksum = ean.slice(0, -1).split('').map(function (char, index) {
return Number(char) * getPositionWeightThroughLengthAndIndex(ean.length, index);
}).reduce(function (acc, partialSum) {
return acc + partialSum;
}, 0);
var remainder = 10 - checksum % 10;
return remainder < 10 ? remainder : 0;
}
/**
* Check if string is valid EAN:
* Matches EAN-8/EAN-13 regex
* Has valid check digit.
*
* @param {string} str
* @return {boolean}
*/


function isEAN(str) {
(0, _assertString.default)(str);
var actualCheckDigit = Number(str.slice(-1));
return validEanRegex.test(str) && actualCheckDigit === calculateCheckDigit(str);
}

module.exports = exports.default;
module.exports.default = exports.default;
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import isIn from './lib/isIn';
import isCreditCard from './lib/isCreditCard';
import isIdentityCard from './lib/isIdentityCard';

import isEAN from './lib/isEAN';
import isISIN from './lib/isISIN';
import isISBN from './lib/isISBN';
import isISSN from './lib/isISSN';
Expand Down Expand Up @@ -160,6 +161,7 @@ const validator = {
isIn,
isCreditCard,
isIdentityCard,
isEAN,
isISIN,
isISBN,
isISSN,
Expand Down
70 changes: 70 additions & 0 deletions src/lib/isEAN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* The most commonly used EAN standard is
* the thirteen-digit EAN-13, while the
* less commonly used 8-digit EAN-8 barcode was
* introduced for use on small packages.
* EAN consists of:
* GS1 prefix, manufacturer code, product code and check digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number
*/

import assertString from './util/assertString';

/**
* Define EAN Lenghts; 8 for EAN-8; 13 for EAN-13
* and Regular Expression for valid EANs (EAN-8, EAN-13),
* with exact numberic matching of 8 or 13 digits [0-9]
*/
const LENGTH_EAN_8 = 8;
const validEanRegex = /^(\d{8}|\d{13})$/;


/**
* Get position weight given:
* EAN length and digit index/position
*
* @param {number} length
* @param {number} index
* @return {number}
*/
function getPositionWeightThroughLengthAndIndex(length, index) {
if (length === LENGTH_EAN_8) {
return (index % 2 === 0) ? 3 : 1;
}

return (index % 2 === 0) ? 1 : 3;
}

/**
* Calculate EAN Check Digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number#Calculation_of_checksum_digit
*
* @param {string} ean
* @return {number}
*/
function calculateCheckDigit(ean) {
const checksum = ean
.slice(0, -1)
.split('')
.map((char, index) => Number(char) * getPositionWeightThroughLengthAndIndex(ean.length, index))
.reduce((acc, partialSum) => acc + partialSum, 0);

const remainder = 10 - (checksum % 10);

return remainder < 10 ? remainder : 0;
}

/**
* Check if string is valid EAN:
* Matches EAN-8/EAN-13 regex
* Has valid check digit.
*
* @param {string} str
* @return {boolean}
*/
export default function isEAN(str) {
assertString(str);
const actualCheckDigit = Number(str.slice(-1));

return validEanRegex.test(str) && actualCheckDigit === calculateCheckDigit(str);
}
19 changes: 19 additions & 0 deletions test/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -3475,6 +3475,25 @@ describe('Validators', () => {
});
});

it('should validate EANs', () => {
test({
validator: 'isEAN',
valid: [
'9421023610112',
'1234567890128',
'4012345678901',
'9771234567003',
'9783161484100',
'73513537',
],
invalid: [
'5901234123451',
'079777681629',
'0705632085948',
],
});
});

it('should validate ISSNs', () => {
test({
validator: 'isISSN',
Expand Down
70 changes: 68 additions & 2 deletions validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
}(this, (function () { 'use strict';

function _typeof(obj) {
"@babel/helpers - typeof";

if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
Expand Down Expand Up @@ -1446,6 +1444,73 @@ function isIdentityCard(str, locale) {
throw new Error("Invalid locale '".concat(locale, "'"));
}

/**
* The most commonly used EAN standard is
* the thirteen-digit EAN-13, while the
* less commonly used 8-digit EAN-8 barcode was
* introduced for use on small packages.
* EAN consists of:
* GS1 prefix, manufacturer code, product code and check digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number
*/
/**
* Define EAN Lenghts; 8 for EAN-8; 13 for EAN-13
* and Regular Expression for valid EANs (EAN-8, EAN-13),
* with exact numberic matching of 8 or 13 digits [0-9]
*/

var LENGTH_EAN_8 = 8;
var validEanRegex = /^(\d{8}|\d{13})$/;
/**
* Get position weight given:
* EAN length and digit index/position
*
* @param {number} length
* @param {number} index
* @return {number}
*/

function getPositionWeightThroughLengthAndIndex(length, index) {
if (length === LENGTH_EAN_8) {
return index % 2 === 0 ? 3 : 1;
}

return index % 2 === 0 ? 1 : 3;
}
/**
* Calculate EAN Check Digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number#Calculation_of_checksum_digit
*
* @param {string} ean
* @return {number}
*/


function calculateCheckDigit(ean) {
var checksum = ean.slice(0, -1).split('').map(function (_char, index) {
return Number(_char) * getPositionWeightThroughLengthAndIndex(ean.length, index);
}).reduce(function (acc, partialSum) {
return acc + partialSum;
}, 0);
var remainder = 10 - checksum % 10;
return remainder < 10 ? remainder : 0;
}
/**
* Check if string is valid EAN:
* Matches EAN-8/EAN-13 regex
* Has valid check digit.
*
* @param {string} str
* @return {boolean}
*/


function isEAN(str) {
assertString(str);
var actualCheckDigit = Number(str.slice(-1));
return validEanRegex.test(str) && actualCheckDigit === calculateCheckDigit(str);
}

var isin = /^[A-Z]{2}[0-9A-Z]{9}[0-9]$/;
function isISIN(str) {
assertString(str);
Expand Down Expand Up @@ -2313,6 +2378,7 @@ var validator = {
isIn: isIn,
isCreditCard: isCreditCard,
isIdentityCard: isIdentityCard,
isEAN: isEAN,
isISIN: isISIN,
isISBN: isISBN,
isISSN: isISSN,
Expand Down
2 changes: 1 addition & 1 deletion validator.min.js

Large diffs are not rendered by default.

0 comments on commit fc253c4

Please sign in to comment.